Suricata源码分析-规则加载模块
规则加载之前的数据获取
- 程序调用
LoadYamlConfig函数从 suricata.yaml 的配置文件中,获取到所有配置保存到节点树中,其中加载规则需要用到的包括:规则文件的默认路径default-rule-path,以及每个规则文件的名字rule-files ParseCommandLine函数中解析命令行参数,当有s/S选项时,需要配置对应的规则变量
// 指定规则文件,并于yaml设置的规则文件一起加载
case 's':
if (suri->sig_file != NULL) {
SCLogError(SC_ERR_CMD_LINE, "can't have multiple -s options or mix -s and -S.");
return TM_ECODE_FAILED;
}
suri->sig_file = optarg;
break;
// 以独占的方式加载指定的规则文件
case 'S':
if (suri->sig_file != NULL) {
SCLogError(SC_ERR_CMD_LINE, "can't have multiple -S options or mix -s and -S.");
return TM_ECODE_FAILED;
}
suri->sig_file = optarg;
suri->sig_file_exclusive = TRUE;
break;
开始加载规则
PostConfLoadedDetectSetup(加载引擎的部分配置文件)
规则加载的入口函数通过SuricataMain() ->PostConfLoadedDetectSetup() 调用,截取PostConfLoadedDetectSetup 函数中关键代码:
// 判断检测引擎类型是NORMAL
if (de_ctx->type == DETECT_ENGINE_TYPE_NORMAL) {
// 加载规则集,并判断结果
if (LoadSignatures(de_ctx, suri) != TM_ECODE_OK)
exit(EXIT_FAILURE);
}
LoadSignatures(规则加载入口)
该函数是规则加载的主入口,调用SigLoadSignatures函数从文件中加载规则,并判断加载结果,输出log信息。函数原型如下:
static int LoadSignatures(DetectEngineCtx *de_ctx, SCInstance *suri)
{
if (SigLoadSignatures(de_ctx, suri->sig_file, suri->sig_file_exclusive) < 0) {
// 加载失败,输出错误提示,并返回结果
SCLogError(SC_ERR_NO_RULES_LOADED, "Loading signatures failed.");
if (de_ctx->failure_fatal)
return TM_ECODE_FAILED;
}
return TM_ECODE_OK;
}
SigLoadSignatures(规则加载主函数)
主要逻辑是在yaml配置给出的规则默认目录下,找到所有匹配的文件并加载规则;或者通过命令行-s/-S指定文件加载规则;在命令行使用 suricatasc -c reload-rules 重载规则时,也会调用到这个函数。加载成功后,会输出提示信息(已加载的规则文件和规则条数)。函数原型如下:
// 参数1:指向检测引擎数据的指针
// 参数2:保存sigatures的文件名,由命令行参数传进来的
// 参数3:参数2中记录的文件是否应该被单独加载
int SigLoadSignatures(
DetectEngineCtx *de_ctx, char *sig_file, int sig_file_exclusive)
截取部分代码(该分支是从yaml配置中加载规则文件,也是主要修改的部分):
// 通过默认的规则文件路径加载规则(当`sig_file`为空或者`sig_file_exclusive`为false时)
/* ok, let's load signature files from the general config */
if (!(sig_file != NULL && sig_file_exclusive == TRUE)) {
// 获取.rules文件节点
rule_files = ConfGetNode(varname);
if (rule_files != NULL) {
// 检查如果是序列,判断规则文件中的文件名是否符合要求
if (!ConfNodeIsSequence(rule_files)) {
SCLogWarning(SC_ERR_INVALID_ARGUMENT,
"Invalid rule-files configuration section: "
"expected a list of filenames.");
}
// 通过检测
else {
TAILQ_FOREACH(file, &rule_files->head, next) {
// 解析配置节点树,获取默认规则路径
sfile = DetectLoadCompleteSigPath(de_ctx, file->val);
good_sigs = bad_sigs = 0;
// 加载所有匹配的规则文件
ret = ProcessSigFiles(de_ctx, sfile, sig_stat, &good_sigs, &bad_sigs);
SCFree(sfile);
if (de_ctx->failure_fatal && ret != 0) {
/* Some rules failed to load, just exit as
* errors would have already been logged. */
exit(EXIT_FAILURE);
}
if (good_sigs == 0) {
SCLogConfig("No rules loaded from %s.", file->val);
}
}
}
}
}
ProcessSigFiles(加载所有规则文件)
ProcessSigFiles 是上述代码中的关键函数,函数原型如下:
/*brief:从每一个匹配的文件中读取规则保存到检测引擎上下文中,并输出提示信息
* param de_ctx:指向检测引擎上下文的指针
* param pattern:保存规则的文件名
* param st:指向规则文件加载状态结构体的指针
* param good_sigs:加载成功的规则数
* param bad_sigs:加载失败的规则数
*/
static int ProcessSigFiles(DetectEngineCtx *de_ctx, char *pattern,
SigFileLoaderStat *st, int *good_sigs, int *bad_sigs)
{
// 返回值
int r = 0;
// 检查参数
if (pattern == NULL) {
SCLogError(SC_ERR_INVALID_ARGUMENT, "opening rule file null");
return -1;
}
#ifdef HAVE_GLOB_H
glob_t files;
r = glob(pattern, 0, NULL, &files);
/* 检测pattern这个目录是否可用,返回值r等于0表示可用
files是传出参数,保存了一些基本信息 */
if (r == GLOB_NOMATCH) {
SCLogWarning(SC_ERR_NO_RULES, "No rule files match the pattern %s", pattern);
++(st->bad_files);
++(st->total_files);
return -1;
} else if (r != 0) {
SCLogError(SC_ERR_OPENING_RULE_FILE, "error expanding template %s: %s",
pattern, strerror(errno));
return -1;
}
// files.gl_pathc是匹配到的文件个数
// 这个循环检测每一个匹配到的文件是否为空
for (size_t i = 0; i < (size_t)files.gl_pathc; i++) {
char *fname = files.gl_pathv[i];
if (strcmp("/dev/null", fname) == 0)
continue;
#else
char *fname = pattern;
if (strcmp("/dev/null", fname) == 0)
return 0;
#endif
// 开始加载文件
SCLogConfig("Loading rule file: %s", fname);
// 加载单个规则文件,并给结果赋值
r = DetectLoadSigFile(de_ctx, fname, good_sigs, bad_sigs);
if (r < 0) {
++(st->bad_files);
}
++(st->total_files);
st->good_sigs_total += *good_sigs;
st->bad_sigs_total += *bad_sigs;
#ifdef HAVE_GLOB_H
}
globfree(&files);
#endif
return r;
}
DetectLoadSigFile(加载单个规则文件)
ProcessSigFiles 函数中调用DetectLodSigFile 函数,加载单个的规则文件。函数从文件中按行读取规则,然后对规则进行检测,判读是否符合格式和关键字,以及规则是否唯一等要求。
- 通过检测后,规则会被保存在类型为 Signature 的单向链表中,之后会进行潜在问题的检测并打印警告。
- 检测不通过,输出错误原因,并将错误规则信息追加到 加载统计结构体 的错误规则队列中。
static int DetectLoadSigFile(DetectEngineCtx *de_ctx, char *sig_file,
int *goodsigs, int *badsigs)
{
Signature *sig = NULL; // 规则结构体指针
int good = 0, bad = 0; // 规则数
char line[DETECT_MAX_RULE_SIZE] = ""; // 保存规则体的字符数组
size_t offset = 0; // 偏移,和line结合使用
int lineno = 0, multiline = 0; // 行号以及多行计数
// 由参数传进来的规则数,先清零
(*goodsigs) = 0;
(*badsigs) = 0;
// 打开规则文件,并检测是否打开成功
FILE *fp = fopen(sig_file, "r");
if (fp == NULL) {
SCLogError(SC_ERR_OPENING_RULE_FILE, "opening rule file %s:"
" %s.", sig_file, strerror(errno));
return -1;
}
// 按行读取规则到line中,进入循环进行后续操作
while(fgets(line + offset, (int)sizeof(line) - offset, fp) != NULL) {
lineno++; // 行号+1
size_t len = strlen(line); // 计算规则长度,保存在len
// 忽略注释和空行,检查第一个字符
/* ignore comments and empty lines */
if (line[0] == '\n' || line [0] == '\r' || line[0] == ' ' || line[0] == '#' || line[0] == '\t')
continue;
// 检查多行规则
/* Check for multiline rules. */
// 去掉末尾的空字符,调整len长度
while (len > 0 && isspace((unsigned char)line[--len]));
// 检测到末尾是'\'字符,说明是多行规则(一条规则分成了多行)
if (line[len] == '\\') {
multiline++; // 多行计数+1
offset = len; // 设置偏移
// 检测剩余空间
if (offset < sizeof(line) - 1) {
// 还有空间,继续读取规则
/* We have room for more. */
continue;
}
// line中没有更多空间,continue,规则解析失败
/* No more room in line buffer, continue, rule will fail
* to parse. */
}
// 检查是否有换行符,并且删除
/* Check if we have a trailing newline, and remove it */
len = strlen(line);
if (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
line[len - 1] = '\0';
}
// 重置offset
/* Reset offset. */
offset = 0;
// 给检测引擎上下文赋值
de_ctx->rule_file = sig_file;
de_ctx->rule_line = lineno - multiline; // 规则实际行数 = 行号 - 多分出的行数
// 将规则追加到规则链表中
sig = DetectEngineAppendSig(de_ctx, line);
// 如果成功,就……
if (sig != NULL) {
// 判断是否启用规则分析和快速模式规则分析
if (rule_engine_analysis_set || fp_engine_analysis_set) {
// 检索规则快速模式
RetrieveFPForSig(de_ctx, sig);
if (fp_engine_analysis_set) {
// 和下面类似,也是输出信息
EngineAnalysisFP(de_ctx, sig, line);
}
// 检测潜在的规则问题,并打印警告
if (rule_engine_analysis_set) {
EngineAnalysisRules(de_ctx, sig, line);
}
}
SCLogDebug("signature %"PRIu32" loaded", sig->id);
good++;
} else { // 如果失败,那么……
if (!de_ctx->sigerror_silent) {
// 规则解析错误,输出具体信息
SCLogError(SC_ERR_INVALID_SIGNATURE, "error parsing signature \"%s\" from "
"file %s at line %"PRId32"", line, sig_file, lineno - multiline);
// 将信息追加到 签名加载统计信息结构体`sig_stat` 的 错误规则队列`failed_sigs` 中
if (!SigStringAppend(&de_ctx->sig_stat, sig_file, line, de_ctx->sigerror, (lineno - multiline))) {
// 添加失败输出提示
SCLogError(SC_ERR_MEM_ALLOC, "Error adding sig \"%s\" from "
"file %s at line %"PRId32"", line, sig_file, lineno - multiline);
}
// 错误字符串清零
if (de_ctx->sigerror) {
de_ctx->sigerror = NULL;
}
}
if (rule_engine_analysis_set) {
// 输出错误信息
EngineAnalysisRulesFailure(line, sig_file, lineno - multiline);
}
// 规则加载错误,失败计数+1
if (!de_ctx->sigerror_ok) {
bad++;
}
}
multiline = 0;
}
fclose(fp);
*goodsigs = good;
*badsigs = bad;
return 0;
}
DetectEngineAppendSig(追加单条规则)
单条规则的初始化和检测在DetectEngineAppendSig->SigInit 中实现,其中SigInitHelper->SigParse用于规则检测,规则的检测需要依赖于 sigmatch_table 数组(注1)。
注1 : 在引擎的初始化操作中,已经由SigTableSetup 函数,对类型为 SigTableElmt 名为 sigmatch_table 的全局结构体数组进行初始化,这个数组的作用是保存规则关键字
/*brief:解析并追加规则到检测引擎上下文的规则列表中。
* 如果规则是双向匹配<>,则应该追加两条规则(交换两边地址)到列表中。
* 还要处理重复的规则,如果规则重复,请使用最新版本。我们使用sid和
* msg来识别重复的规则。如果两个规则有相同的sid和gid,那么他们是重
* 复的
*/
Signature *DetectEngineAppendSig(DetectEngineCtx *de_ctx, const char *sigstr)
{
// 规则初始化
Signature *sig = SigInit(de_ctx, sigstr);
if (sig == NULL) {
return NULL;
}
/*检查规则是否重复,函数有三种返回值
* 返回0,表示这个规则不是重复的,需要被添加到检测引擎列表中
* 返回1,表示这个规则是重复的,并且列表中已存在的规则不应被此规则替换
* 返回2,表示这个规则是重复的,但是列表中现有的规则应该被此规则替换
*/
int dup_sig = DetectEngineSignatureIsDuplicate(de_ctx, sig);
// 重复的规则应该被舍弃,检查上一个函数的返回值,并针对不同的结果进行处理
if (dup_sig == 1) {
// 提示规则重复,被舍弃
SCLogError(SC_ERR_DUPLICATE_SIG, "Duplicate signature \"%s\"", sigstr);
goto error;
} else if (dup_sig == 2) {
// 规则重复,此规则版本较新,将会替代旧规则
SCLogWarning(SC_ERR_DUPLICATE_SIG, "Signature with newer revision,"
" so the older sig replaced by this new signature \"%s\"",
sigstr);
}
// 如果签名有双向操作符<>,追加两条规则到链表中
if (sig->init_data->init_flags & SIG_FLAG_INIT_BIDIREC) {
if (sig->next != NULL) {
sig->next->next = de_ctx->sig_list;
} else {
goto error;
}
} else { // 其他情况,追加一条规则到链表中
// 如果这个规则是第一个,规则列表应该是空的
/* if this sig is the first one, sig_list should be null */
sig->next = de_ctx->sig_list;
}
de_ctx->sig_list = sig;
/*在函数DetectEngineAppendSig中,规则是前置的,我们总是返回第一个,所以
* 如果规则是双向的,返回的规则将通过next指针指向地址交换的相同的规则
*/
return (dup_sig == 0 || dup_sig == 2) ? sig : NULL;
error:
// 处理错误情况以及释放空间
/* free the 2nd sig bidir may have set up */
if (sig != NULL && sig->next != NULL) {
SigFree(de_ctx, sig->next);
sig->next = NULL;
}
if (sig != NULL) {
SigFree(de_ctx, sig);
}
return NULL;
}

浙公网安备 33010602011771号