subsequent bytes in a stream mode, until it finds a closing tag.
It proceeds and scans the entire message body for inline scripts.
-Enhanced Normalizer is a stateful JavaScript whitespace normalizer.
+Enhanced Normalizer is a stateful JavaScript whitespace and identifiers normalizer.
So, the following whitespace codes will be normalized:
* \u0009 Tab <TAB>
* \u000B Vertical Tab <VT>
* Any other Unicode “space separator” <USP>
* Also including new-line and carriage-return line-break characters
+All JavaScript identifier names will be substituted to unified names with the
+following format: a0 -> z9999. So, the number of unique identifiers available
+is 260000 names per HTTP transaction. If Normalizer overruns the configured
+limit, built-in alert generated. Additionaly, there is a config option to
+specify the limit manually:
+ * http_inspect.js_norm_identifier_depth.
+
Additionally, Normalizer validates the syntax with respect to ECMA-262 Standard,
and checks for restrictions for contents of script elements (since, it is HTML-embedded JavaScript).
PEG_CONCURRENT_SESSIONS, PEG_MAX_CONCURRENT_SESSIONS, PEG_SCRIPT_DETECTION,
PEG_PARTIAL_INSPECT, PEG_EXCESS_PARAMS, PEG_PARAMS, PEG_CUTOVERS, PEG_SSL_SEARCH_ABND_EARLY,
PEG_PIPELINED_FLOWS, PEG_PIPELINED_REQUESTS, PEG_TOTAL_BYTES, PEG_JS_INLINE, PEG_JS_EXTERNAL,
- PEG_JS_BYTES, PEG_COUNT_MAX };
+ PEG_JS_BYTES, PEG_JS_IDENTIFIER, PEG_JS_IDENTIFIER_OVERFLOW, PEG_COUNT_MAX };
// Result of scanning by splitter
enum ScanResult { SCAN_NOT_FOUND, SCAN_NOT_FOUND_ACCELERATE, SCAN_FOUND, SCAN_FOUND_PIECE,
INF_JS_CLOSING_TAG,
INF_JS_CODE_IN_EXTERNAL,
INF_JS_SHORTENED_TAG,
+ INF_JS_IDENTIFIER_OVERFLOW,
INF__MAX_VALUE
};
EVENT_JS_CLOSING_TAG = 267,
EVENT_JS_CODE_IN_EXTERNAL = 268,
EVENT_JS_SHORTENED_TAG = 269,
+ EVENT_JS_IDENTIFIER_OVERFLOW = 270,
EVENT__MAX_VALUE
};
#include "decompress/file_decomp.h"
#include "service_inspectors/http2_inspect/http2_flow_data.h"
+#include "utils/js_identifier_ctx.h"
#include "utils/js_normalizer.h"
#include "http_cutter.h"
HttpModule::decrement_peg_counts(PEG_CONCURRENT_SESSIONS);
#ifndef UNIT_TEST_BUILD
+ if (js_ident_ctx)
+ {
+ update_deallocations(js_ident_ctx->size());
+ delete js_ident_ctx;
+ }
if (js_normalizer)
{
update_deallocations(JSNormalizer::size());
}
#ifndef UNIT_TEST_BUILD
-snort::JSNormalizer& HttpFlowData::acquire_js_ctx()
+void HttpFlowData::reset_js_ident_ctx()
+{
+ if (js_ident_ctx)
+ js_ident_ctx->reset();
+}
+
+snort::JSNormalizer& HttpFlowData::acquire_js_ctx(int32_t ident_depth, size_t norm_depth)
{
if (js_normalizer)
return *js_normalizer;
- js_normalizer = new JSNormalizer();
+ if (!js_ident_ctx)
+ {
+ js_ident_ctx = new JSIdentifierCtx(ident_depth);
+ update_allocations(js_ident_ctx->size());
+ }
+
+ js_normalizer = new JSNormalizer(*js_ident_ctx, norm_depth);
update_allocations(JSNormalizer::size());
return *js_normalizer;
js_normalizer = nullptr;
}
#else
-snort::JSNormalizer& HttpFlowData::acquire_js_ctx() { return *js_normalizer; }
+void HttpFlowData::reset_js_ident_ctx() {}
+snort::JSNormalizer& HttpFlowData::acquire_js_ctx(int32_t, size_t)
+{ return *js_normalizer; }
void HttpFlowData::release_js_ctx() {}
#endif
class HttpMsgSection;
class HttpCutter;
class HttpQueryParser;
+class JSIdentifierCtxBase;
namespace snort
{
bool ssl_search_abandoned = false;
// *** HttpJsNorm
+ JSIdentifierCtxBase* js_ident_ctx = nullptr;
snort::JSNormalizer* js_normalizer = nullptr;
bool js_built_in_event = false;
- snort::JSNormalizer& acquire_js_ctx();
+ void reset_js_ident_ctx();
+ snort::JSNormalizer& acquire_js_ctx(int32_t ident_depth, size_t norm_depth);
void release_js_ctx();
// *** Transaction management including pipelining
params->js_norm_param.max_javascript_whitespaces);
ConfigLogger::log_value("js_normalization_depth",
params->js_norm_param.js_normalization_depth);
+ ConfigLogger::log_value("js_norm_identifier_depth", params->js_norm_param.js_identifier_depth);
ConfigLogger::log_value("bad_characters", bad_chars.c_str());
ConfigLogger::log_value("ignore_unreserved", unreserved_chars.c_str());
ConfigLogger::log_flag("percent_u", params->uri_param.percent_u);
return ret;
}
-HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_, int64_t normalization_depth_) :
+HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_, int64_t normalization_depth_,
+ int32_t identifier_depth_) :
uri_param(uri_param_),
normalization_depth(normalization_depth_),
+ identifier_depth(identifier_depth_),
mpse_otag(nullptr),
mpse_attr(nullptr),
mpse_type(nullptr)
dst_end = buffer + len;
}
- auto& ctx = ssn->acquire_js_ctx();
- ctx.set_depth(normalization_depth);
+ auto& ctx = ssn->acquire_js_ctx(identifier_depth, normalization_depth);
auto ret = js_normalize(ctx, end, dst_end, ptr, dst);
switch (ret)
events->create_event(EVENT_JS_BAD_TOKEN);
ssn->js_built_in_event = true;
break;
+ case JSTokenizer::IDENTIFIER_OVERFLOW:
+ HttpModule::increment_peg_counts(PEG_JS_IDENTIFIER_OVERFLOW);
+ *infractions += INF_JS_IDENTIFIER_OVERFLOW;
+ events->create_event(EVENT_JS_IDENTIFIER_OVERFLOW);
+ ssn->js_built_in_event = true;
+ break;
default:
assert(false);
break;
dst_end = buffer + len;
}
- auto& ctx = ssn->acquire_js_ctx();
- ctx.set_depth(normalization_depth);
+ auto& ctx = ssn->acquire_js_ctx(identifier_depth, normalization_depth);
auto dst_before = dst;
auto ret = js_normalize(ctx, end, dst_end, ptr, dst);
events->create_event(EVENT_JS_BAD_TOKEN);
script_continue = false;
break;
+ case JSTokenizer::IDENTIFIER_OVERFLOW:
+ HttpModule::increment_peg_counts(PEG_JS_IDENTIFIER_OVERFLOW);
+ *infractions += INF_JS_IDENTIFIER_OVERFLOW;
+ events->create_event(EVENT_JS_IDENTIFIER_OVERFLOW);
+ script_continue = false;
+ break;
default:
assert(false);
script_continue = false;
class HttpJsNorm
{
public:
- HttpJsNorm(const HttpParaList::UriParam&, int64_t normalization_depth);
+ HttpJsNorm(const HttpParaList::UriParam&, int64_t normalization_depth,
+ int32_t identifier_depth);
~HttpJsNorm();
void legacy_normalize(const Field& input, Field& output, HttpInfractions*, HttpEventGen*,
const HttpParaList::UriParam& uri_param;
int64_t normalization_depth;
+ int32_t identifier_depth;
bool configure_once = false;
snort::SearchTool* mpse_otag;
"use legacy normalizer to normalize JavaScript in response bodies" },
{ "js_normalization_depth", Parameter::PT_INT, "-1:max53", "0",
- "number of input JavaScript bytes to normalize with enhanced normalizer "
- "(-1 max allowed value) (experimental)" },
+ "enable enhanced normalizer (0 is disabled); "
+ "number of input JavaScript bytes to normalize (-1 unlimited) "
+ "(experimental)" },
+
+ // range of accepted identifier names is (a0:z9999), so the max is 26 * 10000 = 260000
+ { "js_norm_identifier_depth", Parameter::PT_INT, "0:260000", "260000",
+ "max number of unique JavaScript identifiers to normalize" },
{ "max_javascript_whitespaces", Parameter::PT_INT, "1:65535", "200",
"maximum consecutive whitespaces allowed within the JavaScript obfuscated data" },
params->js_norm_param.is_javascript_normalization
or params->js_norm_param.normalize_javascript;
}
+ else if (val.is("js_norm_identifier_depth"))
+ {
+ params->js_norm_param.js_identifier_depth = val.get_int32();
+ }
else if (val.is("js_normalization_depth"))
{
int64_t v = val.get_int64();
ParseError("Cannot use normalize_javascript and js_normalization_depth together.");
if ( params->js_norm_param.is_javascript_normalization )
- params->js_norm_param.js_norm = new HttpJsNorm(params->uri_param, params->js_norm_param.js_normalization_depth);
+ params->js_norm_param.js_norm = new HttpJsNorm(params->uri_param,
+ params->js_norm_param.js_normalization_depth, params->js_norm_param.js_identifier_depth);
params->script_detection_handle = script_detection_handle;
bool normalize_javascript = false;
bool is_javascript_normalization = false;
int64_t js_normalization_depth = 0;
+ int32_t js_identifier_depth = 0;
int max_javascript_whitespaces = 200;
class HttpJsNorm* js_norm = nullptr;
};
transaction->set_request(this);
get_related_sections();
session_data->release_js_ctx();
+ session_data->reset_js_ident_ctx();
}
HttpMsgRequest::~HttpMsgRequest()
{ EVENT_JS_CLOSING_TAG, "unexpected script closing tag in JavaScript" },
{ EVENT_JS_CODE_IN_EXTERNAL, "JavaScript code under the external script tags" },
{ EVENT_JS_SHORTENED_TAG, "script opening tag in a short form" },
+ { EVENT_JS_IDENTIFIER_OVERFLOW, "max number of unique JavaScript identifiers reached" },
{ 0, nullptr }
};
{ CountType::SUM, "js_inline_scripts", "total number of inline JavaScripts processed" },
{ CountType::SUM, "js_external_scripts", "total number of external JavaScripts processed" },
{ CountType::SUM, "js_bytes", "total number of JavaScript bytes processed" },
+ { CountType::SUM, "js_identifiers", "total number of unique JavaScript identifiers processed" },
+ { CountType::SUM, "js_identifier_overflows", "total number of unique JavaScript identifier "
+ "limit overflows" },
{ CountType::END, nullptr, nullptr }
};
long HttpTestManager::print_amount {};
bool HttpTestManager::print_hex {};
-HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_, int64_t normalization_depth_) :
+HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_, int64_t normalization_depth_,
+ int32_t identifier_depth_) :
uri_param(uri_param_), normalization_depth(normalization_depth_),
- mpse_otag(nullptr), mpse_attr(nullptr), mpse_type(nullptr) {}
+ identifier_depth(identifier_depth_), mpse_otag(nullptr), mpse_attr(nullptr),
+ mpse_type(nullptr) {}
HttpJsNorm::~HttpJsNorm() = default;
void HttpJsNorm::configure(){}
int64_t Parameter::get_int(char const*) { return 0; }
void show_stats(PegCount*, const PegInfo*, unsigned, const char*) { }
void show_stats(PegCount*, const PegInfo*, const IndexVec&, const char*, FILE*) { }
-HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_, int64_t normalization_depth_) :
+HttpJsNorm::HttpJsNorm(const HttpParaList::UriParam& uri_param_, int64_t normalization_depth_,
+ int32_t identifier_depth_) :
uri_param(uri_param_), normalization_depth(normalization_depth_),
- mpse_otag(nullptr), mpse_attr(nullptr), mpse_type(nullptr) {}
+ identifier_depth(identifier_depth_), mpse_otag(nullptr), mpse_attr(nullptr),
+ mpse_type(nullptr) {}
HttpJsNorm::~HttpJsNorm() = default;
void HttpJsNorm::configure() {}
int64_t Parameter::get_int(char const*) { return 0; }
dnet_header.h
dyn_array.cc
dyn_array.h
+ js_identifier_ctx.cc
+ js_identifier_ctx.h
js_normalizer.cc
js_normalizer.h
js_tokenizer.h
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// js_identifier_ctx.cc author Oleksandr Serhiienko <oserhiie@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "js_identifier_ctx.h"
+
+#ifndef CATCH_TEST_BUILD
+#include "service_inspectors/http_inspect/http_enum.h"
+#include "service_inspectors/http_inspect/http_module.h"
+#else
+namespace HttpEnums
+{
+enum PEG_COUNT
+{
+ PEG_JS_IDENTIFIER
+};
+}
+
+class HttpModule
+{
+public:
+ static void increment_peg_counts(HttpEnums::PEG_COUNT) {}
+};
+#endif // CATCH_TEST_BUILD
+
+#define FIRST_NAME_SIZE 26
+#define LAST_NAME_SIZE 9999
+
+static const char s_ident_first_names[FIRST_NAME_SIZE] =
+{
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
+};
+
+const char* JSIdentifierCtx::substitute(const char* identifier)
+{
+ const auto it = ident_names.find(identifier);
+ if (it != ident_names.end())
+ return it->second.c_str();
+
+ if (++ident_last_name > LAST_NAME_SIZE)
+ {
+ if (++ident_first_name > FIRST_NAME_SIZE - 1)
+ return nullptr;
+
+ ident_last_name = 0;
+ }
+
+ if (++unique_ident_cnt > depth)
+ return nullptr;
+
+ ident_names[identifier] = s_ident_first_names[ident_first_name]
+ + std::to_string(ident_last_name);
+
+ HttpModule::increment_peg_counts(HttpEnums::PEG_JS_IDENTIFIER);
+ return ident_names[identifier].c_str();
+}
+
+void JSIdentifierCtx::reset()
+{
+ ident_first_name = 0;
+ ident_last_name = -1;
+ unique_ident_cnt = 0;
+ ident_names.clear();
+}
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// js_identifier_ctx.h author Oleksandr Serhiienko <oserhiie@cisco.com>
+
+#ifndef JS_IDENTIFIER_CTX
+#define JS_IDENTIFIER_CTX
+
+#include <string>
+#include <unordered_map>
+
+class JSIdentifierCtxBase
+{
+public:
+ virtual ~JSIdentifierCtxBase() = default;
+
+ virtual const char* substitute(const char* identifier) = 0;
+ virtual void reset() = 0;
+ virtual size_t size() const = 0;
+};
+
+class JSIdentifierCtx : public JSIdentifierCtxBase
+{
+public:
+ JSIdentifierCtx(int32_t depth) : depth(depth) {}
+
+ const char* substitute(const char* identifier) override;
+ void reset() override;
+
+ // approximated to 500 unique mappings insertions
+ size_t size() const override
+ { return (sizeof(JSIdentifierCtx) + (sizeof(std::string) * 2 * 500)); }
+
+private:
+ int ident_first_name = 0;
+ int ident_last_name = -1;
+ int32_t unique_ident_cnt = 0;
+ int32_t depth;
+
+ std::unordered_map<std::string, std::string> ident_names;
+};
+
+#endif // JS_IDENTIFIER_CTX
+
using namespace snort;
-JSNormalizer::JSNormalizer()
- : depth(-1),
- rem_bytes(-1),
+JSNormalizer::JSNormalizer(JSIdentifierCtxBase& js_ident_ctx, size_t norm_depth)
+ : depth(norm_depth),
+ rem_bytes(norm_depth),
unlim(true),
src_next(nullptr),
dst_next(nullptr),
- tokenizer(in, out)
+ tokenizer(in, out, js_ident_ctx)
{
-}
-
-void JSNormalizer::set_depth(size_t new_depth)
-{
- if (depth == new_depth)
- return;
-
- depth = new_depth;
- rem_bytes = depth;
unlim = depth == (size_t)-1;
}
if (!unlim)
rem_bytes -= r_bytes;
src_next = src + r_bytes;
- dst_next = dst + w_bytes;
+
+ // avoid heap overflow if number of written bytes bigger than accepted dst_len
+ dst_next = (w_bytes <= dst_len) ? dst + w_bytes : dst + dst_len;
return rem_bytes ? ret : JSTokenizer::EOS;
}
class JSNormalizer
{
public:
- JSNormalizer();
+ JSNormalizer(JSIdentifierCtxBase& js_ident_ctx, size_t depth);
const char* get_src_next() const
{ return src_next; }
void reset_depth()
{ rem_bytes = depth; }
- void set_depth(size_t depth);
-
JSTokenizer::JSRet normalize(const char* src, size_t src_len, char* dst, size_t dst_len);
static size_t size();
#include "log/messages.h"
+class JSIdentifierCtxBase;
+
class JSTokenizer : public yyFlexLexer
{
private:
SCRIPT_CONTINUE,
OPENING_TAG,
CLOSING_TAG,
- BAD_TOKEN
+ BAD_TOKEN,
+ IDENTIFIER_OVERFLOW
};
- JSTokenizer(std::istream& in, std::ostream& out);
+ JSTokenizer(std::istream& in, std::ostream& out, JSIdentifierCtxBase& ident_ctx);
~JSTokenizer() override;
// returns JSRet
JSRet eval_eof();
JSRet do_spacing(JSToken cur_token);
JSRet do_operator_spacing(JSToken cur_token);
+ JSRet do_identifier_substitution(const char* lexeme);
bool unescape(const char* lexeme);
private:
std::stringstream tmp;
JSToken token = UNDEFINED;
+ JSIdentifierCtxBase& ident_ctx;
};
#endif // JS_TOKENIZER_H
#include "config.h"
#endif
+ #include "utils/js_identifier_ctx.h"
#include "utils/js_tokenizer.h"
#include <cassert>
{KEYWORD} { EXEC(do_spacing(KEYWORD)) ECHO; BEGIN(regst); }
{OPERATOR} { EXEC(do_operator_spacing(OPERATOR)) ECHO; BEGIN(divop); }
{LITERAL} { EXEC(do_spacing(LITERAL)) ECHO; BEGIN(divop); }
-{IDENTIFIER} { if (unescape(YYText())) { EXEC(do_spacing(IDENTIFIER)) ECHO; } BEGIN(divop); }
+{IDENTIFIER} { if (unescape(YYText())) { EXEC(do_spacing(IDENTIFIER)) EXEC(do_identifier_substitution(YYText())) } BEGIN(divop); }
.|{ALL_UNICODE} { ECHO; token = UNDEFINED; BEGIN(INITIAL); }
<<EOF>> { EXEC(eval_eof()) }
// JSTokenizer members
-JSTokenizer::JSTokenizer(std::istream& in, std::ostream& out)
- : yyFlexLexer(in, out)
+JSTokenizer::JSTokenizer(std::istream& in, std::ostream& out, JSIdentifierCtxBase& ident_ctx)
+ : yyFlexLexer(in, out),
+ ident_ctx(ident_ctx)
{
BEGIN(regst);
}
return BAD_TOKEN;
}
+JSTokenizer::JSRet JSTokenizer::do_identifier_substitution(const char* lexeme)
+{
+ const char* ident = ident_ctx.substitute(lexeme);
+
+ if (ident)
+ {
+ yyout << ident;
+ return EOS;
+ }
+
+ return IDENTIFIER_OVERFLOW;
+}
+
bool JSTokenizer::unescape(const char* lexeme)
{
if ( strstr(lexeme, "\\u") )
add_catch_test( js_normalizer_test
SOURCES
${FLEX_js_tokenizer_OUTPUTS}
+ ../js_identifier_ctx.cc
../js_normalizer.cc
../util_cstring.cc
)
+add_catch_test( js_identifier_ctx_test
+ SOURCES
+ ../js_identifier_ctx.cc
+)
+
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// js_identifier_ctx_test.cc author Oleksandr Serhiienko <oserhiie@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "catch/catch.hpp"
+
+#include <cstring>
+#include <vector>
+
+#include "utils/js_identifier_ctx.h"
+
+#define DEPTH 260000
+
+#define FIRST_NAME_SIZE 26
+#define LAST_NAME_SIZE 9999
+
+static const char s_ident_first_names[FIRST_NAME_SIZE] =
+{
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
+};
+
+TEST_CASE("JSIdentifierCtx::substitute()", "[JSIdentifierCtx]")
+{
+ SECTION("same name")
+ {
+ JSIdentifierCtx ident_ctx(DEPTH);
+
+ CHECK(!strcmp(ident_ctx.substitute("a"), "a0"));
+ CHECK(!strcmp(ident_ctx.substitute("a"), "a0"));
+ }
+ SECTION("different names")
+ {
+ JSIdentifierCtx ident_ctx(DEPTH);
+
+ CHECK(!strcmp(ident_ctx.substitute("a"), "a0"));
+ CHECK(!strcmp(ident_ctx.substitute("b"), "a1"));
+ CHECK(!strcmp(ident_ctx.substitute("a"), "a0"));
+ }
+ SECTION("depth reached")
+ {
+ JSIdentifierCtx ident_ctx(2);
+
+ CHECK(!strcmp(ident_ctx.substitute("a"), "a0"));
+ CHECK(!strcmp(ident_ctx.substitute("b"), "a1"));
+ CHECK(ident_ctx.substitute("c") == nullptr);
+ CHECK(ident_ctx.substitute("d") == nullptr);
+ CHECK(!strcmp(ident_ctx.substitute("a"), "a0"));
+ }
+ SECTION("max names")
+ {
+ JSIdentifierCtx ident_ctx(DEPTH + 2);
+
+ std::vector<std::string> n, e;
+ n.reserve(DEPTH + 2);
+ e.reserve(DEPTH);
+
+ for (int it = 0; it < DEPTH + 2; ++it)
+ n.push_back("n" + std::to_string(it));
+
+ for (int it_first = 0; it_first < FIRST_NAME_SIZE; ++it_first)
+ {
+ for (int it_last = 0; it_last <= LAST_NAME_SIZE; ++it_last)
+ e.push_back(s_ident_first_names[it_first] + std::to_string(it_last));
+ }
+
+ for (int it = 0; it < DEPTH; ++it)
+ CHECK(!strcmp(ident_ctx.substitute(n[it].c_str()), e[it].c_str()));
+
+ CHECK(ident_ctx.substitute(n[DEPTH].c_str()) == nullptr);
+ CHECK(ident_ctx.substitute(n[DEPTH + 1].c_str()) == nullptr);
+ }
+}
+
#include <cstring>
+#include "utils/js_identifier_ctx.h"
#include "utils/js_normalizer.h"
namespace snort
{ exit(EXIT_FAILURE); }
}
+class JSIdentifierCtxTest : public JSIdentifierCtxBase
+{
+public:
+ JSIdentifierCtxTest() = default;
+
+ const char* substitute(const char* identifier) override
+ { return identifier; }
+ void reset() override {}
+ size_t size() const override {}
+};
+
using namespace snort;
#define DEPTH 65535
-#define NORMALIZE(src, expected) \
- char dst[sizeof(expected)]; \
- JSNormalizer norm; \
- norm.set_depth(DEPTH); \
- auto ret = norm.normalize(src, sizeof(src), dst, sizeof(dst)); \
- const char* ptr = norm.get_src_next(); \
- int act_len = norm.get_dst_next() - dst; \
+#define NORMALIZE(src, expected) \
+ char dst[sizeof(expected)]; \
+ JSIdentifierCtxTest ident_ctx; \
+ JSNormalizer norm(ident_ctx, DEPTH); \
+ auto ret = norm.normalize(src, sizeof(src), dst, sizeof(dst)); \
+ const char* ptr = norm.get_src_next(); \
+ int act_len = norm.get_dst_next() - dst;
#define VALIDATE(src, expected) \
CHECK(ret == JSTokenizer::SCRIPT_CONTINUE); \
CHECK(act_len == sizeof(expected) - 1); \
CHECK(!memcmp(dst, expected, act_len));
-#define VALIDATE_FAIL(src, expected, ret_code, ptr_offset) \
- CHECK(ret == ret_code); \
- CHECK((ptr - src) == ptr_offset); \
- CHECK(act_len == sizeof(expected) - 1); \
+#define VALIDATE_FAIL(src, expected, ret_code, ptr_offset) \
+ CHECK(ret == ret_code); \
+ CHECK((ptr - src) == ptr_offset); \
+ CHECK(act_len == sizeof(expected) - 1); \
CHECK(!memcmp(dst, expected, act_len));
-#define NORMALIZE_L(src, src_len, dst, dst_len, depth, ret, ptr, len) \
- { \
- JSNormalizer norm; \
- norm.set_depth(depth); \
- ret = norm.normalize(src, src_len, dst, dst_len); \
- ptr = norm.get_src_next(); \
- len = norm.get_dst_next() - dst; \
- } \
+#define NORMALIZE_L(src, src_len, dst, dst_len, depth, ret, ptr, len) \
+ { \
+ JSIdentifierCtxTest ident_ctx; \
+ JSNormalizer norm(ident_ctx, depth); \
+ ret = norm.normalize(src, src_len, dst, dst_len); \
+ ptr = norm.get_src_next(); \
+ len = norm.get_dst_next() - dst; \
+ }
// ClamAV test cases
static const char clamav_buf0[] =
const char* ptr;
int ret;
- JSNormalizer norm;
-
- norm.set_depth(7);
+ JSIdentifierCtxTest ident_ctx;
+ JSNormalizer norm(ident_ctx, 7);
ret = norm.normalize(src, sizeof(src), dst, sizeof(dst));
ptr = norm.get_src_next();
act_len = norm.get_dst_next() - dst;
CHECK(ret == JSTokenizer::SCRIPT_CONTINUE);
CHECK(ptr == src + sizeof(src));
- CHECK(act_len == 12); // size of normalized src
+ CHECK(act_len == 7); // size of normalized src
CHECK(!memcmp(dst, expected, sizeof(dst)));
}
}
VALIDATE_FAIL(unexpected_tag_buf24, unexpected_tag_expected24, JSTokenizer::OPENING_TAG, 39);
}
}
+