Skip to content

Commit

Permalink
detect/iprep: implement isset and isnotset
Browse files Browse the repository at this point in the history
Implement special "isset" and "isnotset" modes.

"isset" matches if an IP address is part of an iprep category with any
value.

It is internally implemented as ">=,0", which should always be true if
there is a value to evaluate, as valid reputation values are 0-127.

"isnotset" matches if an IP address is not part of an iprep category.

Internally it is implemented outside the uint support.

Ticket: OISF#6857.
  • Loading branch information
victorjulien committed Jun 15, 2024
1 parent 3e46c51 commit 83976a4
Show file tree
Hide file tree
Showing 2 changed files with 224 additions and 39 deletions.
68 changes: 48 additions & 20 deletions rust/src/detect/iprep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,17 @@ impl std::str::FromStr for DetectIPRepDataCmd {
}
}

/// value matching is done use `DetectUintData` logic.
/// isset matching is done using special `DetectUintData` value ">= 0"
/// isnotset matching bypasses `DetectUintData` and is handled directly
/// in the match function (in C).
#[derive(Debug)]
#[repr(C)]
pub struct DetectIPRepData {
pub du8: DetectUintData<u8>,
pub cat: u8,
pub cmd: DetectIPRepDataCmd,
pub isnotset: bool, // if true, ignores `du8`
}

pub fn is_alphanumeric_or_slash(chr: char) -> bool {
Expand All @@ -86,33 +91,56 @@ pub fn detect_parse_iprep(i: &str) -> IResult<&str, DetectIPRepData, RuleParseEr
preceded(multispace0, nom7::bytes::complete::is_not(",")),
)(i)?;

if values.len() == 4 {
let cmd = match DetectIPRepDataCmd::from_str(values[0].trim()) {
Ok(val) => val,
Err(_) => return Err(make_error("invalid command".to_string())),
let args = values.len();
if args == 4 || args == 3 {
let cmd = if let Ok(cmd) = DetectIPRepDataCmd::from_str(values[0].trim()) {
cmd
} else {
return Err(make_error("invalid command".to_string()));
};
let name = values[1].trim();
let mode = match detect_parse_uint_mode(values[2].trim()) {
Ok(val) => val.1,
Err(_) => return Err(make_error("invalid mode".to_string())),
let namez = if let Ok(name) = CString::new(name) {
name
} else {
return Err(make_error("invalid name".to_string()));
};

let namez = CString::new(name).unwrap();
let cat = unsafe { SRepCatGetByShortname(namez.as_ptr()) };
if cat == 0 {
return Err(make_error("unknown category".to_string()));
}
let arg1 = match values[3].trim().parse::<u8>() {
Ok(val) => val,
Err(_) => return Err(make_error("invalid value".to_string())),
};
let du8 = DetectUintData::<u8> {
arg1,
arg2: 0,
mode,
};
return Ok((i, DetectIPRepData { du8, cat, cmd }));
} else {

if args == 4 {
let mode = match detect_parse_uint_mode(values[2].trim()) {
Ok(val) => val.1,
Err(_) => return Err(make_error("invalid mode".to_string())),
};

let arg1 = match values[3].trim().parse::<u8>() {
Ok(val) => val,
Err(_) => return Err(make_error("invalid value".to_string())),
};
let du8 = DetectUintData::<u8> {
arg1,
arg2: 0,
mode,
};
return Ok((i, DetectIPRepData { du8, cat, cmd, isnotset: false, }));
} else {
let (isnotset, mode, arg1) = match values[2].trim() {
"isset" => { (false, DetectUintMode::DetectUintModeGte, 0) },
"isnotset" => { (true, DetectUintMode::DetectUintModeEqual, 0) },
_ => { return Err(make_error("invalid mode".to_string())); },
};
let du8 = DetectUintData::<u8> {
arg1,
arg2: 0,
mode,
};
return Ok((i, DetectIPRepData { du8, cat, cmd, isnotset, }));
}
} else if args < 3 {
return Err(make_error("too few arguments".to_string()));
} else {
return Err(make_error("too many arguments".to_string()));
}

Expand Down
195 changes: 176 additions & 19 deletions src/detect-iprep.c
Original file line number Diff line number Diff line change
Expand Up @@ -158,18 +158,37 @@ static int DetectIPRepMatch (DetectEngineThreadCtx *det_ctx, Packet *p,
SCLogDebug("rd->cmd %u", rd->cmd);
switch (rd->cmd) {
case IPRepCmdAny:
val = GetHostRepSrc(p, rd->cat, version);
if (val < 0)
val = SRepCIDRGetIPRepSrc(det_ctx->de_ctx->srepCIDR_ctx, p, rd->cat, version);
if (val >= 0) {
if (DetectU8Match((uint8_t)val, &rd->du8))
if (!rd->isnotset) {
val = GetHostRepSrc(p, rd->cat, version);
if (val < 0)
val = SRepCIDRGetIPRepSrc(det_ctx->de_ctx->srepCIDR_ctx, p, rd->cat, version);
if (val >= 0) {
if (DetectU8Match((uint8_t)val, &rd->du8))
return 1;
}
val = GetHostRepDst(p, rd->cat, version);
if (val < 0)
val = SRepCIDRGetIPRepDst(det_ctx->de_ctx->srepCIDR_ctx, p, rd->cat, version);
if (val >= 0) {
return DetectU8Match((uint8_t)val, &rd->du8);
}
} else {
/* isnotset for any */

val = GetHostRepSrc(p, rd->cat, version);
if (val < 0)
val = SRepCIDRGetIPRepSrc(det_ctx->de_ctx->srepCIDR_ctx, p, rd->cat, version);
if (val < 0) {
return 1;
}
val = GetHostRepDst(p, rd->cat, version);
if (val < 0)
val = SRepCIDRGetIPRepDst(det_ctx->de_ctx->srepCIDR_ctx, p, rd->cat, version);
if (val >= 0) {
return DetectU8Match((uint8_t)val, &rd->du8);
}
val = GetHostRepDst(p, rd->cat, version);
if (val < 0)
val = SRepCIDRGetIPRepDst(det_ctx->de_ctx->srepCIDR_ctx, p, rd->cat, version);
if (val < 0) {
return 1;
}
/* both have a value, so none 'isnotset' */
return 0;
}
break;

Expand All @@ -182,6 +201,10 @@ static int DetectIPRepMatch (DetectEngineThreadCtx *det_ctx, Packet *p,
if (val >= 0) {
return DetectU8Match((uint8_t)val, &rd->du8);
}
/* implied: no value found */
if (rd->isnotset) {
return 1;
}
break;

case IPRepCmdDst:
Expand All @@ -192,19 +215,39 @@ static int DetectIPRepMatch (DetectEngineThreadCtx *det_ctx, Packet *p,
if (val >= 0) {
return DetectU8Match((uint8_t)val, &rd->du8);
}
/* implied: no value found */
if (rd->isnotset) {
return 1;
}
break;

case IPRepCmdBoth:
val = GetHostRepSrc(p, rd->cat, version);
if (val < 0)
if (!rd->isnotset) {
val = GetHostRepSrc(p, rd->cat, version);
if (val < 0)
val = SRepCIDRGetIPRepSrc(det_ctx->de_ctx->srepCIDR_ctx, p, rd->cat, version);
if (val < 0 || DetectU8Match((uint8_t)val, &rd->du8) == 0)
return 0;
val = GetHostRepDst(p, rd->cat, version);
if (val < 0)
val = SRepCIDRGetIPRepDst(det_ctx->de_ctx->srepCIDR_ctx, p, rd->cat, version);
if (val >= 0) {
return DetectU8Match((uint8_t)val, &rd->du8);
}
} else {
val = GetHostRepSrc(p, rd->cat, version);
if (val >= 0)
return 0;
val = SRepCIDRGetIPRepSrc(det_ctx->de_ctx->srepCIDR_ctx, p, rd->cat, version);
if (val < 0 || DetectU8Match((uint8_t)val, &rd->du8) == 0)
return 0;
val = GetHostRepDst(p, rd->cat, version);
if (val < 0)
if (val >= 0)
return 0;
val = GetHostRepDst(p, rd->cat, version);
if (val >= 0)
return 0;
val = SRepCIDRGetIPRepDst(det_ctx->de_ctx->srepCIDR_ctx, p, rd->cat, version);
if (val >= 0) {
return DetectU8Match((uint8_t)val, &rd->du8);
if (val >= 0)
return 0;
return 1;
}
break;
}
Expand Down Expand Up @@ -775,6 +818,118 @@ static int DetectIPRepTest09(void)
PASS;
}

static FILE *DetectIPRepGenerateNetworksDummy3(void)
{
FILE *fd = NULL;
const char *buffer = "192.168.0.0/16,1,127"; // BadHosts

fd = SCFmemopen((void *)buffer, strlen(buffer), "r");
if (fd == NULL)
SCLogDebug("Error with SCFmemopen()");

return fd;
}

static int DetectIPRepTest10(void)
{
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
Signature *sig = NULL;
FILE *fd = NULL;
int r = 0;
Packet *p = UTHBuildPacket((uint8_t *)"lalala", 6, IPPROTO_TCP);
DetectEngineCtx *de_ctx = DetectEngineCtxInit();

HostInitConfig(HOST_QUIET);
memset(&th_v, 0, sizeof(th_v));

FAIL_IF_NULL(de_ctx);
FAIL_IF_NULL(p);

p->src.addr_data32[0] = UTHSetIPv4Address("192.168.0.1");
p->dst.addr_data32[0] = UTHSetIPv4Address("192.168.0.2");
de_ctx->flags |= DE_QUIET;

SRepInit(de_ctx);
SRepResetVersion();

fd = DetectIPRepGenerateCategoriesDummy2();
r = SRepLoadCatFileFromFD(fd);
FAIL_IF(r < 0);

fd = DetectIPRepGenerateNetworksDummy3();
r = SRepLoadFileFromFD(de_ctx->srepCIDR_ctx, fd);
FAIL_IF(r < 0);

sig = DetectEngineAppendSig(de_ctx,
"alert tcp any any -> any any (msg:\"test\"; iprep:src,BadHosts,isset; sid:1; rev:1;)");
FAIL_IF_NULL(sig);

SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);

SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
FAIL_IF_NOT(p->alerts.cnt == 1);

UTHFreePacket(p);

DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);

HostShutdown();
PASS;
}

static int DetectIPRepTest11(void)
{
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
Signature *sig = NULL;
FILE *fd = NULL;
int r = 0;
Packet *p = UTHBuildPacket((uint8_t *)"lalala", 6, IPPROTO_TCP);
DetectEngineCtx *de_ctx = DetectEngineCtxInit();

HostInitConfig(HOST_QUIET);
memset(&th_v, 0, sizeof(th_v));

FAIL_IF_NULL(de_ctx);
FAIL_IF_NULL(p);

p->src.addr_data32[0] = UTHSetIPv4Address("10.0.0.1");
p->dst.addr_data32[0] = UTHSetIPv4Address("10.0.0.2");
de_ctx->flags |= DE_QUIET;

SRepInit(de_ctx);
SRepResetVersion();

fd = DetectIPRepGenerateCategoriesDummy2();
r = SRepLoadCatFileFromFD(fd);
FAIL_IF(r < 0);

fd = DetectIPRepGenerateNetworksDummy3();
r = SRepLoadFileFromFD(de_ctx->srepCIDR_ctx, fd);
FAIL_IF(r < 0);

sig = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any (msg:\"test\"; "
"iprep:src,BadHosts,isnotset; sid:1; rev:1;)");
FAIL_IF_NULL(sig);

SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);

SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
FAIL_IF_NOT(p->alerts.cnt == 1);

UTHFreePacket(p);

DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);

HostShutdown();
PASS;
}

/**
* \brief this function registers unit tests for IPRep
*/
Expand All @@ -789,5 +944,7 @@ void IPRepRegisterTests(void)
UtRegisterTest("DetectIPRepTest07", DetectIPRepTest07);
UtRegisterTest("DetectIPRepTest08", DetectIPRepTest08);
UtRegisterTest("DetectIPRepTest09", DetectIPRepTest09);
UtRegisterTest("DetectIPRepTest10 -- isset", DetectIPRepTest10);
UtRegisterTest("DetectIPRepTest11 -- isnotset", DetectIPRepTest11);
}
#endif /* UNITTESTS */

0 comments on commit 83976a4

Please sign in to comment.