Skip to content
Open
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
49 changes: 46 additions & 3 deletions apache2/re.c
Original file line number Diff line number Diff line change
Expand Up @@ -986,12 +986,53 @@
return action;
}

/**
* Helper function for action_exists. It checks if "name=value" is present in table for supplied action.
*/
static int apr_table_action_exists(apr_pool_t* p, const apr_table_t* vartable, const char* action, const char* name, const char* value) {
if (strcmp(name, action) != 0) return 0;
Comment on lines +992 to +993
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The helper is named apr_table_action_exists, which reads like an APR API and is very close to apr_table_* symbols. To avoid confusion (and potential future naming collisions in backports), consider renaming it to something module-specific (e.g., msre_table_action_value_exists).

Copilot uses AI. Check for mistakes.

const char* vars = apr_table_getm(p, vartable, name);
if (!vars) return 0;

char pattern[200];
apr_snprintf(pattern, sizeof(pattern), "(?:^|,)%.185s(?:,|$)", value);

char* error_msg = NULL;
msc_regex_t* regex = msc_pregcomp(p, pattern, 0, NULL, NULL);
if (regex == NULL) return 0;

return (msc_regexec(regex, vars, strlen(vars), &error_msg) >= 0);
Comment on lines +993 to +1005
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The duplicate-detection pattern truncates the action value to 185 chars ("%.185s"), which changes equality semantics: two different long values that share a prefix will be treated as duplicates and the later one will be dropped. This can silently change rule behavior for long tag/initcol/etc parameters. Prefer exact string comparison against existing table entries (iterate apr_table_elts) instead of building a bounded regex pattern.

Suggested change
if (strcmp(name, action) != 0) return 0;
const char* vars = apr_table_getm(p, vartable, name);
if (!vars) return 0;
char pattern[200];
apr_snprintf(pattern, sizeof(pattern), "(?:^|,)%.185s(?:,|$)", value);
char* error_msg = NULL;
msc_regex_t* regex = msc_pregcomp(p, pattern, 0, NULL, NULL);
if (regex == NULL) return 0;
return (msc_regexec(regex, vars, strlen(vars), &error_msg) >= 0);
const apr_array_header_t *arr = NULL;
const apr_table_entry_t *elts = NULL;
int i = 0;
apr_size_t value_len = 0;
(void)p;
if (strcmp(name, action) != 0) return 0;
if ((vartable == NULL) || (value == NULL)) return 0;
value_len = strlen(value);
arr = apr_table_elts(vartable);
if (arr == NULL) return 0;
elts = (const apr_table_entry_t *)arr->elts;
for (i = 0; i < arr->nelts; i++) {
const char *vars = NULL;
const char *segment_start = NULL;
const char *cursor = NULL;
if ((elts[i].key == NULL) || (strcmp(elts[i].key, name) != 0)) {
continue;
}
vars = elts[i].val;
if (vars == NULL) {
continue;
}
segment_start = vars;
cursor = vars;
for (;;) {
if ((*cursor == ',') || (*cursor == '\0')) {
apr_size_t segment_len = (apr_size_t)(cursor - segment_start);
if ((segment_len == value_len) &&
(strncmp(segment_start, value, value_len) == 0)) {
return 1;
}
if (*cursor == '\0') {
break;
}
segment_start = cursor + 1;
}
cursor++;
}
}
return 0;

Copilot uses AI. Check for mistakes.
Comment on lines +998 to +1005
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex is built directly from the action value without escaping regex metacharacters. Values like ".", "*", "[" or "\" will change the match behavior (or even make the pattern invalid), causing duplicates to be incorrectly detected or missed. Instead of regex, compare raw strings in the table; if regex must be used, escape the value before compiling.

Suggested change
char pattern[200];
apr_snprintf(pattern, sizeof(pattern), "(?:^|,)%.185s(?:,|$)", value);
char* error_msg = NULL;
msc_regex_t* regex = msc_pregcomp(p, pattern, 0, NULL, NULL);
if (regex == NULL) return 0;
return (msc_regexec(regex, vars, strlen(vars), &error_msg) >= 0);
{
const char *entry = vars;
size_t value_len = strlen(value);
while (*entry != '\0') {
const char *separator = strchr(entry, ',');
size_t entry_len = (separator != NULL) ? (size_t)(separator - entry) : strlen(entry);
if ((entry_len == value_len) && (strncmp(entry, value, value_len) == 0)) {
return 1;
}
if (separator == NULL) {
break;
}
entry = separator + 1;
}
}
return 0;

Copilot uses AI. Check for mistakes.
Comment on lines +993 to +1005
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation allocates a merged string via apr_table_getm() and compiles a new regex for every checked entry. Since msre_parse_generic() uses long-lived pools (ruleset/actionset pools), these allocations persist for the life of the process and can cause significant startup CPU and memory growth on large rulesets. Consider a non-allocating approach: iterate apr_table_elts(vartable) and strcmp existing values for the given key.

Suggested change
if (strcmp(name, action) != 0) return 0;
const char* vars = apr_table_getm(p, vartable, name);
if (!vars) return 0;
char pattern[200];
apr_snprintf(pattern, sizeof(pattern), "(?:^|,)%.185s(?:,|$)", value);
char* error_msg = NULL;
msc_regex_t* regex = msc_pregcomp(p, pattern, 0, NULL, NULL);
if (regex == NULL) return 0;
return (msc_regexec(regex, vars, strlen(vars), &error_msg) >= 0);
const apr_array_header_t *arr;
const apr_table_entry_t *te;
int i;
if (strcmp(name, action) != 0) return 0;
(void)p;
arr = apr_table_elts(vartable);
if (arr == NULL || arr->nelts == 0) return 0;
te = (const apr_table_entry_t *)arr->elts;
for (i = 0; i < arr->nelts; i++) {
if ((te[i].key != NULL) && (te[i].val != NULL)
&& (strcmp(te[i].key, name) == 0)
&& (strcmp(te[i].val, value) == 0))
{
return 1;
}
}
return 0;

Copilot uses AI. Check for mistakes.
}

/**
* Checks if "name=value" is present in table for tags, logdata (and others).
*/
static int action_exists(apr_pool_t* p, const apr_table_t* vartable, const char* name, const char* value) {
/* logdata & msg cannot be used because ',' is used as entries separators */
if (apr_table_action_exists(p, vartable, "capture", name, value)) return 1;
Comment on lines +1008 to +1013
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says this checks for duplicates for "tags, logdata (and others)", but logdata (and msg) are explicitly not handled by this code path. Please update the comment to match actual behavior (or extend the implementation if logdata is intended to be deduped).

Copilot uses AI. Check for mistakes.
if (apr_table_action_exists(p, vartable, "chain", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "initcol", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "multiMatch", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "phase", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "sanitizeArg", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "sanitizeMatched", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "sanitizeMatchedBytes", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "sanitizeRequestHeader", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "sanitizeResponseHeader", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "setrsc", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "setsid", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "setuid", name, value)) return 1;
if (apr_table_action_exists(p, vartable, "tag", name, value)) return 1;
return 0;
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation in this block mixes tabs and spaces (e.g., the return 0; line). Please use the file’s existing indentation style consistently to avoid churn in future diffs.

Suggested change
return 0;
return 0;

Copilot uses AI. Check for mistakes.
}

/**
* Generic parser that is used as basis for target and action parsing.
* It breaks up the input string into name-parameter pairs and places
* them into the given table.
*/
int msre_parse_generic(apr_pool_t *mp, const char *text, apr_table_t *vartable,

Check failure on line 1035 in apache2/re.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 58 to the 25 allowed.

See more on https://sonarcloud.io/project/issues?id=owasp-modsecurity_ModSecurity&issues=AZ0Bnmhv7o15W-M7xHZi&open=AZ0Bnmhv7o15W-M7xHZi&pullRequest=3518
char **error_msg)
{
char *p = (char *)text;
Expand Down Expand Up @@ -1103,9 +1144,11 @@
value = apr_pstrmemdup(mp, value, p - value);
}

/* add to table */
apr_table_addn(vartable, name, value);
count++;
/* add to table (only if not already present) */
if (!action_exists(mp, vartable, name, value)) {
apr_table_addn(vartable, name, value);
count++;
}
Comment on lines +1147 to +1151
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are new behavior changes here (silently dropping duplicate action parameters like tag) but the regression test suite currently has a TODO for tag metadata actions (tests/regression/action/00-meta.t). Please add a regression test that asserts duplicate tag entries don't appear multiple times in the audit log metadata output, so this behavior is locked in and doesn't regress.

Copilot uses AI. Check for mistakes.

/* move to the first character of the next name-value pair */
while(isspace(*p)||(*p == ',')||(*p == '|')) p++;
Expand Down
Loading