Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/userguide/rules/payload-keywords.rst
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,12 @@ When ``relative`` is included, there must be a previous ``content`` or ``pcre``

Note: if ``oper`` is ``/`` and the divisor is 0, there will never be a match on the ``byte_math`` keyword.

Note: if ``oper`` is ``<<`` or ``>>`` and ``rvalue`` is 64 or greater, the result is 0.
A rule that gives such an ``rvalue`` as a number still loads, with a warning, unless
``byte_math`` is passed to ``--strict-rule-keywords``, which makes the rule fail to
load instead. When ``rvalue`` names a variable the count is only known once the rule
runs.

The result can be stored in a result variable and referenced by
other rule options later in the rule.

Expand Down
17 changes: 16 additions & 1 deletion rust/src/detect/byte_math.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (C) 2022 Open Information Security Foundation
/* Copyright (C) 2022-2026 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
Expand Down Expand Up @@ -617,6 +617,21 @@ mod tests {
);
}

#[test]
// a shift count of 64 or more parses; the rule loads with a warning from
// DetectByteMathSetup() and the shift yields 0 at match time
fn test_parser_shift_rvalue() {
assert!(parse_bytemath("bytes 4, offset 3933, oper >>, rvalue 63, result foo").is_ok());
assert!(parse_bytemath("bytes 4, offset 3933, oper <<, rvalue 63, result foo").is_ok());
assert!(parse_bytemath("bytes 4, offset 3933, oper >>, rvalue 64, result foo").is_ok());
assert!(parse_bytemath("bytes 4, offset 3933, oper <<, rvalue 64, result foo").is_ok());
assert!(parse_bytemath("bytes 4, offset 3933, oper >>, rvalue 100, result foo").is_ok());
assert!(parse_bytemath("bytes 4, offset 3933, oper +, rvalue 100, result foo").is_ok());
assert!(
parse_bytemath("bytes 4, offset 3933, oper >>, rvalue myrvalue, result foo").is_ok()
);
}

#[test]
fn test_parser_bitmask_invalid() {
assert!(parse_bytemath(
Expand Down
90 changes: 88 additions & 2 deletions src/detect-bytemath.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (C) 2020-2022 Open Information Security Foundation
/* Copyright (C) 2020-2026 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
Expand Down Expand Up @@ -189,7 +189,11 @@ int DetectByteMathDoMatch(DetectEngineThreadCtx *det_ctx, const DetectByteMathDa
}
break;
case RightShift:
val >>= rvalue;
if (rvalue < 64) {
val >>= rvalue;
} else {
val = 0;
}
break;
}

Expand Down Expand Up @@ -293,6 +297,31 @@ static int DetectByteMathSetup(DetectEngineCtx *de_ctx, Signature *s, const char
if (data == NULL)
goto error;

/* A shift of 64 or more clears the 64 bit value being shifted, so the
* result is 0 for every packet. Only a literal rvalue can be checked
* here; a variable one is read from the payload at match time. */
if ((data->oper == LeftShift || data->oper == RightShift) &&
!(data->flags & DETECT_BYTEMATH_FLAG_RVALUE_VAR) && data->rvalue >= 64) {
if (SigMatchStrictEnabled(DETECT_BYTEMATH)) {
SCLogError("byte_math rvalue %u is 64 or more, so \"%s\" always gives 0", data->rvalue,
data->oper == LeftShift ? "<<" : ">>");
goto error;
}
if (s->id > 0) {
SCLogWarning("signature sid:%u: byte_math rvalue %u is 64 or more, so \"%s\" "
"always gives 0",
s->id, data->rvalue, data->oper == LeftShift ? "<<" : ">>");
} else if (de_ctx->rule_file != NULL) {
SCLogWarning("signature at %s:%u: byte_math rvalue %u is 64 or more, so \"%s\" "
"always gives 0",
de_ctx->rule_file, de_ctx->rule_line, data->rvalue,
data->oper == LeftShift ? "<<" : ">>");
} else {
SCLogWarning("byte_math rvalue %u is 64 or more, so \"%s\" always gives 0",
data->rvalue, data->oper == LeftShift ? "<<" : ">>");
}
}

int sm_list;
if (s->init_data->list != DETECT_SM_LIST_NOTSET) {
if (DetectBufferGetActiveList(de_ctx, s) == -1)
Expand Down Expand Up @@ -376,6 +405,8 @@ static int DetectByteMathSetup(DetectEngineCtx *de_ctx, Signature *s, const char
SCLogError("unknown byte_ keyword var seen in byte_math - %s", rvalue);
goto error;
}
/* rvalue becomes a byte_values[] index here, so a check on the
* literal count has to run above this point. */
data->rvalue = index;
data->flags |= DETECT_BYTEMATH_FLAG_RVALUE_VAR;
SCFree(rvalue);
Expand Down Expand Up @@ -1001,6 +1032,59 @@ static int DetectByteMathPacket02(void)
PASS;
}

/**
* \test A payload-supplied shift count of 64 or more yields 0 instead of
* shifting a uint64_t by its own width.
*/
static int DetectByteMathPacket03(void)
{
/* byte 0 is the shift count (64), byte 1 the value shifted, byte 2 the
* expected result */
uint8_t buf[] = { 0x40, 0xff, 0x00 };

Packet *p = UTHBuildPacket(buf, sizeof(buf), IPPROTO_UDP);
FAIL_IF_NULL(p);

/* 0xff >> 64 is 0 */
FAIL_IF_NOT(UTHPacketMatchSig(p, "alert udp any any -> any any "
"(byte_extract: 1, 0, shift;"
"byte_math: bytes 1, offset 1, oper >>, rvalue shift, result "
"var;"
"byte_test: 1, =, var, 2;"
"sid:1;)"));
UTHFreePacket(p);

PASS;
}

/**
* \test A literal shift count of 64 or more parses and keeps its value, so
* DetectByteMathSetup() can warn about it.
*/
static int DetectByteMathParseTest17(void)
{
DetectByteMathData *bmd = DetectByteMathParse(
NULL, "bytes 4, offset 2, oper >>, rvalue 64, result foo", NULL, NULL);
FAIL_IF_NULL(bmd);
FAIL_IF_NOT(bmd->oper == RightShift);
FAIL_IF_NOT(bmd->rvalue == 64);
DetectByteMathFree(NULL, bmd);

bmd = DetectByteMathParse(
NULL, "bytes 4, offset 2, oper <<, rvalue 100, result foo", NULL, NULL);
FAIL_IF_NULL(bmd);
FAIL_IF_NOT(bmd->oper == LeftShift);
FAIL_IF_NOT(bmd->rvalue == 100);
DetectByteMathFree(NULL, bmd);

bmd = DetectByteMathParse(
NULL, "bytes 4, offset 2, oper >>, rvalue 63, result foo", NULL, NULL);
FAIL_IF_NULL(bmd);
DetectByteMathFree(NULL, bmd);

PASS;
}

static int DetectByteMathContext01(void)
{
DetectEngineCtx *de_ctx = NULL;
Expand Down Expand Up @@ -1071,8 +1155,10 @@ static void DetectByteMathRegisterTests(void)
UtRegisterTest("DetectByteMathParseTest14", DetectByteMathParseTest14);
UtRegisterTest("DetectByteMathParseTest15", DetectByteMathParseTest15);
UtRegisterTest("DetectByteMathParseTest16", DetectByteMathParseTest16);
UtRegisterTest("DetectByteMathParseTest17", DetectByteMathParseTest17);
UtRegisterTest("DetectByteMathPacket01", DetectByteMathPacket01);
UtRegisterTest("DetectByteMathPacket02", DetectByteMathPacket02);
UtRegisterTest("DetectByteMathPacket03", DetectByteMathPacket03);
UtRegisterTest("DetectByteMathContext01", DetectByteMathContext01);
}
#endif /* UNITTESTS */
3 changes: 3 additions & 0 deletions src/util-affinity.c
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,9 @@ static int BuildCpuset(const char *name, SCConfNode *node, cpu_set_t *cpu)
*/
static const char *GetAffinitySetName(const char *val)
{
if (val == NULL) {
return NULL;
}
if (strcmp(val, "decode-cpu-set") == 0 || strcmp(val, "stream-cpu-set") == 0 ||
strcmp(val, "reject-cpu-set") == 0 || strcmp(val, "output-cpu-set") == 0) {
return NULL;
Expand Down
53 changes: 47 additions & 6 deletions src/util-flow-rate.c
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ static inline void FlowRateStoreUpdateCurrentRing(
static inline void FlowRateStoreFlushRing(
FlowRateStore *frs, SCTime_t p_ts, uint32_t pkt_len, int direction)
{
memset(frs->dir[direction].buf, 0, frs->dir[direction].size);
memset(frs->dir[direction].buf, 0, frs->dir[direction].size * sizeof(*frs->dir[direction].buf));
frs->dir[direction].last_idx = 0;
frs->dir[direction].start_ts = p_ts;
frs->dir[direction].buf[0] = pkt_len;
Expand Down Expand Up @@ -388,7 +388,8 @@ static int FlowRateTest03(void)
PASS;
}

/* Test to check update of buffer if new pkt comes out of the window */
/* Test to check update of buffer if new pkt comes out of the window, and that
* flushing clears the whole ring rather than only its first bytes. */
static int FlowRateTest04(void)
{
SC_ATOMIC_SET(flow_config.memcap, 10000);
Expand All @@ -409,13 +410,53 @@ static int FlowRateTest04(void)
FAIL_IF(frs->dir[0].start_ts.secs != p1->ts.secs);

Packet *p2 = UTHBuildPacket((uint8_t *)"DATA", 4, IPPROTO_TCP);
p2->ts.secs = p1->ts.secs + 60;
p2->ts.secs = p1->ts.secs + 1;
FlowRateStoreUpdate(frs, p2->ts, GET_PKT_LEN(p2), TOSERVER);

/* Total length of packet is 44 */
FAIL_IF(frs->dir[0].sum != 44);
FAIL_IF(frs->dir[0].sum != 92);
FAIL_IF(frs->dir[0].last_ts.secs != p2->ts.secs);
FAIL_IF(frs->dir[0].buf[0] != 44);
FAIL_IF(frs->dir[0].start_ts.secs != p2->ts.secs);
FAIL_IF(frs->dir[0].buf[1] != 44);
FAIL_IF(frs->dir[0].start_ts.secs != p1->ts.secs);

Packet *p3 = UTHBuildPacket((uint8_t *)"ABababa", 7, IPPROTO_TCP);
p3->ts.secs = p1->ts.secs + 2;
FlowRateStoreUpdate(frs, p3->ts, GET_PKT_LEN(p3), TOSERVER);

/* Total length of packet is 47 */
FAIL_IF(frs->dir[0].sum != 139);
FAIL_IF(frs->dir[0].last_ts.secs != p3->ts.secs);
FAIL_IF(frs->dir[0].buf[2] != 47);
FAIL_IF(frs->dir[0].start_ts.secs != p1->ts.secs);

/* Silence of a full interval, so this packet is out of the window and
* flushes the ring */
Packet *p4 = UTHBuildPacket((uint8_t *)"nmn", 3, IPPROTO_TCP);
p4->ts.secs = p3->ts.secs + 4;
FlowRateStoreUpdate(frs, p4->ts, GET_PKT_LEN(p4), TOSERVER);

/* Total length of packet is 43 */
FAIL_IF(frs->dir[0].sum != 43);
FAIL_IF(frs->dir[0].last_ts.secs != p4->ts.secs);
FAIL_IF(frs->dir[0].start_ts.secs != p4->ts.secs);
FAIL_IF(frs->dir[0].buf[0] != 43);

/* Every other slot must be cleared too, not just the first bytes */
FAIL_IF(frs->dir[0].buf[1] != 0);
FAIL_IF(frs->dir[0].buf[2] != 0);
FAIL_IF(frs->dir[0].buf[3] != 0);

Packet *p5 = UTHBuildPacket((uint8_t *)"yoohoo", 6, IPPROTO_TCP);
p5->ts.secs = p4->ts.secs + 2;
FlowRateStoreUpdate(frs, p5->ts, GET_PKT_LEN(p5), TOSERVER);

/* Total length of packet is 46 */
FAIL_IF(frs->dir[0].sum != 89);
FAIL_IF(frs->dir[0].last_ts.secs != p5->ts.secs);
FAIL_IF(frs->dir[0].start_ts.secs != p4->ts.secs);
FAIL_IF(frs->dir[0].buf[0] != 43);
FAIL_IF(frs->dir[0].buf[1] != 0);
FAIL_IF(frs->dir[0].buf[2] != 46);

FlowRateStoreFree(frs);
PASS;
Expand Down
Loading