struct State
{
State() : data(), root(), selector(nullptr), node(nullptr), waypoint(0),
- original_waypoint(0), sid(0), packet_number(0), opt_parent(false)
+ original_waypoint(0), delta(0), sid(0), packet_number(0), opt_parent(false), re_eval(false)
{
for (uint8_t i = 0; i < NUM_IPS_OPTIONS_VARS; ++i)
byte_extract_vars[i] = 0;
}
State(const detection_option_tree_node_t& n, const detection_option_eval_data_t& d,
- snort::IpsOption* s, unsigned wp, uint64_t id, bool p) : data(d),
+ snort::IpsOption* s, unsigned wp, unsigned dt, uint64_t id, bool p, bool r_e) : data(d),
root(1, d.otn),
selector(s), node(const_cast<detection_option_tree_node_t*>(&n)), waypoint(wp),
- original_waypoint(wp), sid(id), packet_number(d.p->context->packet_number),
- opt_parent(p)
+ original_waypoint(wp), delta(dt), sid(id), packet_number(d.p->context->packet_number),
+ opt_parent(p), re_eval(r_e)
{
for (uint8_t i = 0; i < NUM_IPS_OPTIONS_VARS; ++i)
snort::GetVarValueByIndex(&byte_extract_vars[i], i);
detection_option_tree_node_t* node;
unsigned waypoint;
const unsigned original_waypoint;
+ unsigned delta;
uint64_t sid;
uint64_t packet_number;
uint32_t byte_extract_vars[NUM_IPS_OPTIONS_VARS];
bool opt_parent;
+ bool re_eval;
};
using LState = snort::GroupedList<State>;
}
cursor.set_pos(waypoint);
+ cursor.set_delta(delta);
if (cursor.awaiting_data(true) or cursor.size() == 0)
{
const detection_option_tree_node_t* root_node = root.children[0];
- if (opt_parent)
+ cursor.set_re_eval(re_eval);
+
+ if (!opt_parent)
{
- for (int i = 0; i < root_node->num_children; ++i)
- result += detection_option_node_evaluate(root_node->children[i], data, cursor);
+ assert(!re_eval);
+ result = detection_option_node_evaluate(root_node, data, cursor);
}
- else
+ else if (re_eval)
{
result = detection_option_node_evaluate(root_node, data, cursor);
+ root_node->state[snort::get_instance_id()].last_check.ts = {};
+ }
+ else
+ {
+ for (int i = 0; i < root_node->num_children; ++i)
+ result += detection_option_node_evaluate(root_node->children[i], data, cursor);
}
if (data.leaf_reached and !data.otn->sigInfo.file_id)
auto selector = data.buf_selector;
auto pos = cursor.get_next_pos();
auto sid = cursor.id();
+ auto delta = cursor.get_delta();
auto nst = node.state + snort::get_instance_id();
assert(nst);
if (states_cnt < states_cnt_max)
{
++states_cnt;
- new LState(states, (LState*&)nst->conts, node, data, selector, pos, sid, opt_parent);
+ new LState(states, (LState*&)nst->conts, node, data, selector, pos, delta, sid, opt_parent, cursor.is_re_eval());
}
else
{
st->leave_group();
delete st;
- new LState(states, (LState*&)nst->conts, node, data, selector, pos, sid, opt_parent);
+ new LState(states, (LState*&)nst->conts, node, data, selector, pos, delta, sid, opt_parent, cursor.is_re_eval());
}
snort::pc.cont_creations++;
// this is the current version of the base api
// must be prefixed to subtype version
-#define BASE_API_VERSION 18
+#define BASE_API_VERSION 19
// set options to API_OPTIONS to ensure compatibility
#ifndef API_OPTIONS
extensible = rhs.extensible;
buf_id = rhs.buf_id;
is_accumulated = rhs.is_accumulated;
+ re_eval = rhs.re_eval;
+
+ if (re_eval)
+ delta = rhs.delta;
if (rhs.data)
{
CHECK(r1);
CHECK(!r2);
}
+
+ SECTION("Delta")
+ {
+ SECTION("Set delta in bounds")
+ {
+ bool r1 = false;
+ unsigned d1 = sizeof(buf_1) - 1;
+
+ cursor.set("1", buf_1, sizeof(buf_1), true);
+ r1 = cursor.set_delta(d1);
+
+ CHECK(r1);
+ CHECK(cursor.get_delta() == d1);
+ }
+ SECTION("Set delta as buffer size")
+ {
+ bool r1 = false;
+ unsigned d1 = sizeof(buf_1);
+
+ cursor.set("1", buf_1, sizeof(buf_1), true);
+ r1 = cursor.set_delta(d1);
+
+ CHECK(r1);
+ CHECK(cursor.get_delta() == d1);
+ }
+ SECTION("Set delta bigger than buffer size")
+ {
+ bool r1 = false;
+ unsigned d1 = sizeof(buf_1) + 1;
+
+ cursor.set("1", buf_1, sizeof(buf_1), true);
+ r1 = cursor.set_delta(d1);
+
+ CHECK(!r1);
+ CHECK(cursor.get_delta() == d1);
+ }
+ SECTION("Set delta as negative")
+ {
+ bool r1 = false;
+ unsigned d1 = -1;
+
+ cursor.set("1", buf_1, sizeof(buf_1), true);
+ r1 = cursor.set_delta(d1);
+
+ CHECK(!r1);
+ CHECK(cursor.get_delta() == d1);
+ }
+ SECTION("Copy constructor")
+ {
+ SECTION("re_eval false")
+ {
+ cursor.set("1", buf_1, sizeof(buf_1), true);
+ cursor.set_delta(sizeof(buf_1) - 1);
+ cursor.set_re_eval(false);
+ unsigned r = Cursor(cursor).get_delta();
+ CHECK(r != cursor.get_delta());
+ CHECK(r == 0);
+ }
+ SECTION("re_eval true")
+ {
+ cursor.set("1", buf_1, sizeof(buf_1), true);
+ unsigned d1 = sizeof(buf_1) - 1;
+ cursor.set_delta(d1);
+ cursor.set_re_eval(true);
+
+ Cursor c = Cursor(cursor);
+ CHECK(c.get_delta() == cursor.get_delta());
+ CHECK(c.get_delta() == d1);
+ bool r1 = c.is_re_eval();
+ CHECK(r1);
+ }
+ }
+ }
}
#endif
bool set_pos(unsigned n)
{
current_pos = n;
+ re_eval = false;
return !(current_pos > buf_size);
}
bool set_delta(unsigned n)
{
- if (n > buf_size)
- return false;
delta = n;
- return true;
+ return n <= buf_size;
}
void set_data(CursorData* cd);
return current_pos - buf_size;
}
+ bool is_re_eval() const
+ { return re_eval; }
+
+ void set_re_eval(bool val)
+ { re_eval = val; }
+
typedef std::vector<CursorData*> CursorDataVec;
private:
bool extensible = false; // if the buffer could have more data in a continuation
uint64_t buf_id = 0; // source buffer ID
bool is_accumulated = false;
+ bool re_eval = false;
};
#endif
ContentData* config;
};
-bool ContentOption::retry(Cursor& c, const Cursor&)
+static inline bool retry(const PatternMatchData& pmd, const Cursor& c)
{
- if ( config->pmd.is_negated() )
+ if ( pmd.is_negated() )
+ return false;
+
+ // since we're already out-of-bound, no reason to retry
+ if ( c.get_pos() >= c.size() )
return false;
- if ( !config->pmd.depth )
+ if ( !pmd.depth )
return true;
- return c.get_delta() + config->pmd.pattern_size <= config->pmd.depth;
+ return c.get_delta() + pmd.pattern_size <= pmd.depth;
+}
+
+bool ContentOption::retry(Cursor& c, const Cursor&)
+{
+ return ::retry(config->pmd, c);
}
uint32_t ContentOption::hash() const
{
// byte_extract variables are strictly unsigned, used for sizes and forward offsets
// converting from uint32_t to int64_t ensures all extracted values remain positive
- int64_t offset, depth;
+ int64_t offset, depth, orig_depth;
if (cd->offset_var >= 0 && cd->offset_var < NUM_IPS_OPTIONS_VARS)
{
{
uint32_t extract;
GetVarValueByIndex(&extract, cd->depth_var);
- depth = extract;
+ orig_depth = depth = extract;
}
else
- depth = cd->pmd.depth;
+ orig_depth = depth = cd->pmd.depth;
uint32_t file_pos = c.get_file_pos();
int64_t pos = 0;
- if ( !c.get_delta() )
+ if ( !c.get_delta() and !c.is_re_eval() )
{
// first - adjust from cursor or buffer start
pos = (cd->pmd.is_relative() ? c.get_pos() : 0) + offset;
return false;
}
- if ( ( cd->depth_configured and depth <= 0 ) or pos + cd->pmd.pattern_size > c.size() )
+ if ( (cd->depth_configured and depth <= 0) or !c.size() )
+ return false;
+
+ unsigned last_position = c.size() - 1;
+
+ if ( last_position < pos )
+ {
+ if ( !cd->pmd.is_negated() )
+ {
+ unsigned next_pkt_pos = pos - last_position + c.size();
+
+ c.set_pos(next_pkt_pos);
+ c.set_re_eval(true);
+ }
+
+ return false;
+ }
+
+ unsigned min_bytes_to_match = pos + cd->pmd.pattern_size;
+
+ if ( min_bytes_to_match > c.size() )
+ {
+ constexpr unsigned current_byte_evaluated = 1;
+ unsigned depth_skipped = last_position - pos + current_byte_evaluated;
+
+ if ( !cd->pmd.is_negated() && depth >= depth_skipped + cd->pmd.pattern_size )
+ {
+ assert(cd->pmd.pattern_size >= depth_skipped);
+
+ // IPS content takes into account repeated parts of
+ // the pattern during retries. But in next PDU, such influence
+ // is harmful, so some extra bytes are needed
+ unsigned extra_offset = cd->pmd.pattern_size - cd->match_delta;
+ unsigned next_pkt_pos = extra_offset + c.size();
+
+ c.set_pos(next_pkt_pos);
+ c.set_re_eval(true);
+ c.set_delta(depth_skipped);
+ }
+
return false;
+ }
int64_t bytes_left = c.size() - pos;
return true;
}
+ else if ( cd->depth_configured )
+ {
+ unsigned used_depth = depth + c.get_delta();
+
+ if ( orig_depth <= used_depth + cd->pmd.pattern_size )
+ return false;
+
+ // Continuation should be created only on the last evaluation of current node,
+ // where node means current ips option during detection process
+ if ( c.get_delta() == 0 and !c.is_re_eval() and !retry(cd->pmd, c) )
+ return false;
+
+ unsigned next_pkt_pos = c.size() + 0;
+
+ c.set_pos(next_pkt_pos);
+ c.set_re_eval(true);
+ c.set_delta(used_depth);
+ }
return false;
}