Suricata源码分析-规则加载模块

规则加载之前的数据获取

  1. 程序调用LoadYamlConfig 函数从 suricata.yaml 的配置文件中,获取到所有配置保存到节点树中,其中加载规则需要用到的包括:规则文件的默认路径default-rule-path,以及每个规则文件的名字rule-files
  2. 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 函数,加载单个的规则文件。函数从文件中按行读取规则,然后对规则进行检测,判读是否符合格式和关键字,以及规则是否唯一等要求。

  1. 通过检测后,规则会被保存在类型为 Signature 的单向链表中,之后会进行潜在问题的检测并打印警告。
  2. 检测不通过,输出错误原因,并将错误规则信息追加到 加载统计结构体 的错误规则队列中。
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;
}
posted @ 2022-04-22 14:14  6c696e  阅读(613)  评论(0)    收藏  举报