diff --git a/docs/developer/developer.adoc b/docs/developer/developer.adoc
index 08273e24d..823a1504e 100644
--- a/docs/developer/developer.adoc
+++ b/docs/developer/developer.adoc
@@ -317,6 +317,8 @@ behaviour.
* *OSCAP_FULL_VALIDATION=1* - validate all exported documents (slower)
* *SEXP_VALIDATE_DISABLE=1* - do not validate SEXP expressions (faster)
+* *OSCAP_PCRE_EXEC_RECURSION_LIMIT* - override default recursion limit
+ for match in pcre_exec call in textfilecontent(54) probes.
diff --git a/src/OVAL/probes/independent/textfilecontent54_probe.c b/src/OVAL/probes/independent/textfilecontent54_probe.c
index 1c449833f..3053f5d95 100644
--- a/src/OVAL/probes/independent/textfilecontent54_probe.c
+++ b/src/OVAL/probes/independent/textfilecontent54_probe.c
@@ -52,68 +52,11 @@
#include <probe/option.h>
#include <oval_fts.h>
#include "common/debug_priv.h"
+#include "common/util.h"
#include "textfilecontent54_probe.h"
#define FILE_SEPARATOR '/'
-static int get_substrings(char *str, int *ofs, pcre *re, int want_substrs, char ***substrings) {
- int i, ret, rc;
- int ovector[60], ovector_len = sizeof (ovector) / sizeof (ovector[0]);
- char **substrs;
-
- // todo: max match count check
-
- for (i = 0; i < ovector_len; ++i)
- ovector[i] = -1;
-
-#if defined(OS_SOLARIS)
- rc = pcre_exec(re, NULL, str, strlen(str), *ofs, PCRE_NO_UTF8_CHECK, ovector, ovector_len);
-#else
- rc = pcre_exec(re, NULL, str, strlen(str), *ofs, 0, ovector, ovector_len);
-#endif
-
- if (rc < -1) {
- dE("Function pcre_exec() failed to match a regular expression with return code %d on string '%s'.", rc, str);
- return rc;
- } else if (rc == -1) {
- /* no match */
- return 0;
- }
-
- *ofs = (*ofs == ovector[1]) ? ovector[1] + 1 : ovector[1];
-
- if (!want_substrs) {
- /* just report successful match */
- return 1;
- }
-
- ret = 0;
- if (rc == 0) {
- /* vector too small */
- // todo: report partial results
- rc = ovector_len / 3;
- }
-
- substrs = malloc(rc * sizeof (char *));
- for (i = 0; i < rc; ++i) {
- int len;
- char *buf;
-
- if (ovector[2 * i] == -1)
- continue;
- len = ovector[2 * i + 1] - ovector[2 * i];
- buf = malloc(len + 1);
- memcpy(buf, str + ovector[2 * i], len);
- buf[len] = '\0';
- substrs[ret] = buf;
- ++ret;
- }
-
- *substrings = substrs;
-
- return ret;
-}
-
static SEXP_t *create_item(const char *path, const char *filename, char *pattern,
int instance, char **substrs, int substr_cnt, oval_schema_version_t over)
{
@@ -260,7 +203,7 @@ static int process_file(const char *prefix, const char *path, const char *file,
want_instance = 0;
SEXP_free(next_inst);
- substr_cnt = get_substrings(buf, &ofs, pfd->compiled_regex, want_instance, &substrs);
+ substr_cnt = oscap_get_substrings(buf, &ofs, pfd->compiled_regex, want_instance, &substrs);
if (substr_cnt < 0) {
SEXP_t *msg;
diff --git a/src/OVAL/probes/independent/textfilecontent_probe.c b/src/OVAL/probes/independent/textfilecontent_probe.c
index 9abf8fcc3..988a6471d 100644
--- a/src/OVAL/probes/independent/textfilecontent_probe.c
+++ b/src/OVAL/probes/independent/textfilecontent_probe.c
@@ -71,63 +71,11 @@
#include <probe/option.h>
#include <oval_fts.h>
#include "common/debug_priv.h"
+#include "common/util.h"
#include "textfilecontent_probe.h"
#define FILE_SEPARATOR '/'
-static int get_substrings(char *str, pcre *re, int want_substrs, char ***substrings) {
- int i, ret, rc;
- int ovector[60], ovector_len = sizeof (ovector) / sizeof (ovector[0]);
-
- // todo: max match count check
-
- for (i = 0; i < ovector_len; ++i)
- ovector[i] = -1;
-
- rc = pcre_exec(re, NULL, str, strlen(str), 0, 0,
- ovector, ovector_len);
-
- if (rc < -1) {
- return -1;
- } else if (rc == -1) {
- /* no match */
- return 0;
- } else if(!want_substrs) {
- /* just report successful match */
- return 1;
- }
-
- char **substrs;
-
- ret = 0;
- if (rc == 0) {
- /* vector too small */
- rc = ovector_len / 3;
- }
-
- substrs = malloc(rc * sizeof (char *));
- for (i = 0; i < rc; ++i) {
- int len;
- char *buf;
-
- if (ovector[2 * i] == -1)
- continue;
- len = ovector[2 * i + 1] - ovector[2 * i];
- buf = malloc(len + 1);
- memcpy(buf, str + ovector[2 * i], len);
- buf[len] = '\0';
- substrs[ret] = buf;
- ++ret;
- }
- /*
- if (ret < rc)
- substrs = realloc(substrs, ret * sizeof (char *));
- */
- *substrings = substrs;
-
- return ret;
-}
-
static SEXP_t *create_item(const char *path, const char *filename, char *pattern,
int instance, char **substrs, int substr_cnt, oval_schema_version_t over)
{
@@ -244,9 +192,10 @@ static int process_file(const char *prefix, const char *path, const char *filena
int cur_inst = 0;
char line[4096];
+ int ofs = 0;
while (fgets(line, sizeof(line), fp) != NULL) {
- substr_cnt = get_substrings(line, re, 1, &substrs);
+ substr_cnt = oscap_get_substrings(line, &ofs, re, 1, &substrs);
if (substr_cnt > 0) {
int k;
SEXP_t *item;
diff --git a/src/common/util.c b/src/common/util.c
index 146b7bc39..8f130c50e 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -30,11 +30,13 @@
#include <limits.h>
#include <stdarg.h>
#include <math.h>
+#include <pcre.h>
#include "util.h"
#include "_error.h"
#include "oscap.h"
#include "oscap_helpers.h"
+#include "debug_priv.h"
#ifdef OS_WINDOWS
#include <stdlib.h>
@@ -45,6 +47,7 @@
#endif
#define PATH_SEPARATOR '/'
+#define OSCAP_PCRE_EXEC_RECURSION_LIMIT_DEFAULT 5000
int oscap_string_to_enum(const struct oscap_string_map *map, const char *str)
{
@@ -353,6 +356,76 @@ char *oscap_path_join(const char *path1, const char *path2)
return joined_path;
}
+int oscap_get_substrings(char *str, int *ofs, pcre *re, int want_substrs, char ***substrings) {
+ int i, ret, rc;
+ int ovector[60], ovector_len = sizeof (ovector) / sizeof (ovector[0]);
+ char **substrs;
+
+ // todo: max match count check
+
+ for (i = 0; i < ovector_len; ++i) {
+ ovector[i] = -1;
+ }
+
+ struct pcre_extra extra;
+ extra.match_limit_recursion = OSCAP_PCRE_EXEC_RECURSION_LIMIT_DEFAULT;
+ char *limit_str = getenv("OSCAP_PCRE_EXEC_RECURSION_LIMIT");
+ if (limit_str != NULL) {
+ unsigned long limit;
+ if (sscanf(limit_str, "%lu", &limit) == 1) {
+ extra.match_limit_recursion = limit;
+ }
+ }
+ extra.flags = PCRE_EXTRA_MATCH_LIMIT_RECURSION;
+#if defined(OS_SOLARIS)
+ rc = pcre_exec(re, &extra, str, strlen(str), *ofs, PCRE_NO_UTF8_CHECK, ovector, ovector_len);
+#else
+ rc = pcre_exec(re, &extra, str, strlen(str), *ofs, 0, ovector, ovector_len);
+#endif
+
+ if (rc < -1) {
+ dE("Function pcre_exec() failed to match a regular expression with return code %d on string '%s'.", rc, str);
+ return rc;
+ } else if (rc == -1) {
+ /* no match */
+ return 0;
+ }
+
+ *ofs = (*ofs == ovector[1]) ? ovector[1] + 1 : ovector[1];
+
+ if (!want_substrs) {
+ /* just report successful match */
+ return 1;
+ }
+
+ ret = 0;
+ if (rc == 0) {
+ /* vector too small */
+ // todo: report partial results
+ rc = ovector_len / 3;
+ }
+
+ substrs = malloc(rc * sizeof (char *));
+ for (i = 0; i < rc; ++i) {
+ int len;
+ char *buf;
+
+ if (ovector[2 * i] == -1) {
+ continue;
+ }
+ len = ovector[2 * i + 1] - ovector[2 * i];
+ buf = malloc(len + 1);
+ memcpy(buf, str + ovector[2 * i], len);
+ buf[len] = '\0';
+ substrs[ret] = buf;
+ ++ret;
+ }
+
+ *substrings = substrs;
+
+ return ret;
+}
+
#ifdef OS_WINDOWS
char *oscap_windows_wstr_to_str(const wchar_t *wstr)
{
diff --git a/src/common/util.h b/src/common/util.h
index 50a1c746f..2592f3962 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -31,6 +31,7 @@
#include "public/oscap.h"
#include <stdarg.h>
#include <string.h>
+#include <pcre.h>
#include "oscap_export.h"
#ifndef __attribute__nonnull__
@@ -467,6 +468,19 @@ int oscap_strncasecmp(const char *s1, const char *s2, size_t n);
*/
char *oscap_strerror_r(int errnum, char *buf, size_t buflen);
+/**
+ * Match a regular expression and return substrings.
+ * Caller is responsible for freeing the returned array.
+ * @param str subject string
+ * @param ofs starting offset in str
+ * @param re compiled regular expression
+ * @param want_substrs if non-zero, substrings will be returned
+ * @param substrings contains returned substrings
+ * @return count of matched substrings, 0 if no match
+ * negative value on failure
+ */
+int oscap_get_substrings(char *str, int *ofs, pcre *re, int want_substrs, char ***substrings);
+
#ifdef OS_WINDOWS
/**
* Convert wide character string to a C string (UTF-16 to UTF-8)
diff --git a/tests/probes/textfilecontent54/30-ospp-v42.rules b/tests/probes/textfilecontent54/30-ospp-v42.rules
new file mode 100644
index 000000000..7ad0c254d
--- /dev/null
+++ b/tests/probes/textfilecontent54/30-ospp-v42.rules
@@ -0,0 +1,113 @@
+## The purpose of these rules is to meet the requirements for Operating
+## System Protection Profile (OSPP)v4.2. These rules depends on having
+## 10-base-config.rules, 11-loginuid.rules, and 43-module-load.rules installed.
+
+## Unsuccessful file creation (open with O_CREAT)
+-a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-create
+-a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-create
+-a always,exit -F arch=b32 -S open -F a1&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-create
+-a always,exit -F arch=b64 -S open -F a1&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-create
+-a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-create
+-a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-create
+-a always,exit -F arch=b32 -S open -F a1&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-create
+-a always,exit -F arch=b64 -S open -F a1&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-create
+-a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-create
+-a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-create
+-a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-create
+-a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-create
+
+## Unsuccessful file modifications (open for write or truncate)
+-a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-modification
+-a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-modification
+-a always,exit -F arch=b32 -S open -F a1&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-modification
+-a always,exit -F arch=b64 -S open -F a1&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-modification
+-a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-modification
+-a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-modification
+-a always,exit -F arch=b32 -S open -F a1&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-modification
+-a always,exit -F arch=b64 -S open -F a1&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-modification
+-a always,exit -F arch=b32 -S truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-modification
+-a always,exit -F arch=b64 -S truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-modification
+-a always,exit -F arch=b32 -S truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-modification
+-a always,exit -F arch=b64 -S truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-modification
+
+## Unsuccessful file access (any other opens) This has to go last.
+-a always,exit -F arch=b32 -S open,creat,truncate,ftruncate,openat,open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-access
+-a always,exit -F arch=b64 -S open,creat,truncate,ftruncate,openat,open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-access
+-a always,exit -F arch=b32 -S open,creat,truncate,ftruncate,openat,open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-access
+-a always,exit -F arch=b64 -S open,creat,truncate,ftruncate,openat,open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-access
+
+## Unsuccessful file delete
+-a always,exit -F arch=b32 -S unlink,unlinkat,rename,renameat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-delete
+-a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-delete
+-a always,exit -F arch=b32 -S unlink,unlinkat,rename,renameat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-delete
+-a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-delete
+
+## Unsuccessful permission change
+-a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-perm-change
+-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-perm-change
+-a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-perm-change
+-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-perm-change
+
+## Unsuccessful ownership change
+-a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-perm-change
+-a always,exit -F arch=b64 -S lchown,fchown,chown,fchownat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-perm-change
+-a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-perm-change
+-a always,exit -F arch=b64 -S lchown,fchown,chown,fchownat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccesful-perm-change
+
+## User add delete modify. This is covered by pam. However, someone could
+## open a file and directly create or modify a user, so we'll watch passwd and
+## shadow for writes
+-a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
+-a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
+-a always,exit -F arch=b32 -S open -F a1&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
+-a always,exit -F arch=b64 -S open -F a1&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
+-a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify
+-a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify
+-a always,exit -F arch=b32 -S open -F a1&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify
+-a always,exit -F arch=b64 -S open -F a1&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify
+
+## User enable and disable. This is entirely handled by pam.
+
+## Group add delete modify. This is covered by pam. However, someone could
+## open a file and directly create or modify a user, so we'll watch group and
+## gshadow for writes
+-a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&03 -F path=/etc/group -F auid>=1000 -F auid!=unset -F key=group-modify
+-a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&03 -F path=/etc/group -F auid>=1000 -F auid!=unset -F key=group-modify
+-a always,exit -F arch=b32 -S open -F a1&03 -F path=/etc/group -F auid>=1000 -F auid!=unset -F key=group-modify
+-a always,exit -F arch=b64 -S open -F a1&03 -F path=/etc/group -F auid>=1000 -F auid!=unset -F key=group-modify
+-a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&03 -F path=/etc/gshadow -F auid>=1000 -F auid!=unset -F key=group-modify
+-a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&03 -F path=/etc/gshadow -F auid>=1000 -F auid!=unset -F key=group-modify
+-a always,exit -F arch=b32 -S open -F a1&03 -F path=/etc/gshadow -F auid>=1000 -F auid!=unset -F key=group-modify
+-a always,exit -F arch=b64 -S open -F a1&03 -F path=/etc/gshadow -F auid>=1000 -F auid!=unset -F key=group-modify
+
+## Use of special rights for config changes. This would be use of setuid
+## programs that relate to user accts. This is not all setuid apps because
+## requirements are only for ones that affect system configuration.
+-a always,exit -F path=/usr/sbin/usernetctl -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
+-a always,exit -F path=/usr/sbin/seunshare -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
+-a always,exit -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
+-a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
+-a always,exit -F path=/usr/bin/newuidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
+-a always,exit -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
+-a always,exit -F path=/usr/bin/newgidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
+-a always,exit -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
+-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
+-a always,exit -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
+-a always,exit -F path=/usr/bin/at -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
+
+## Privilege escalation via su or sudo. This is entirely handled by pam.
+
+## Audit log access
+-a always,exit -F dir=/var/log/audit/ -F perm=r -F auid>=1000 -F auid!=unset -F key=access-audit-trail
+
+## Software updates. This is entirely handled by rpm.
+
+## System start and shutdown. This is entirely handled by systemd
+
+## Kernel Module loading. This is handled in 43-module-load.rules
+
+## Application invocation. The requirements list an optional requirement
+## FPT_SRP_EXT.1 Software Restriction Policies. This event is intended to
+## state results from that policy. This would be handled entirely by
+## that daemon.
+
diff --git a/tests/probes/textfilecontent54/CMakeLists.txt b/tests/probes/textfilecontent54/CMakeLists.txt
index 87c6e215d..48bbde0e6 100644
--- a/tests/probes/textfilecontent54/CMakeLists.txt
+++ b/tests/probes/textfilecontent54/CMakeLists.txt
@@ -1,4 +1,5 @@
if(ENABLE_PROBES_INDEPENDENT)
add_oscap_test("all.sh")
add_oscap_test("test_filecontent_non_utf.sh")
+ add_oscap_test("test_recursion_limit.sh")
endif()
diff --git a/tests/probes/textfilecontent54/test_recursion_limit.oval.xml b/tests/probes/textfilecontent54/test_recursion_limit.oval.xml
new file mode 100644
index 000000000..6f6a5ba14
--- /dev/null
+++ b/tests/probes/textfilecontent54/test_recursion_limit.oval.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<oval_definitions xmlns:oval-def="http://oval.mitre.org/XMLSchema/oval-definitions-5" xmlns:oval="http://oval.mitre.org/XMLSchema/oval-common-5" xmlns:ind="http://oval.mitre.org/XMLSchema/oval-definitions-5#independent" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ind-def="http://oval.mitre.org/XMLSchema/oval-definitions-5#independent" xmlns:unix-def="http://oval.mitre.org/XMLSchema/oval-definitions-5#unix" xmlns:lin-def="http://oval.mitre.org/XMLSchema/oval-definitions-5#linux" xmlns="http://oval.mitre.org/XMLSchema/oval-definitions-5" xsi:schemaLocation="http://oval.mitre.org/XMLSchema/oval-definitions-5#unix unix-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5#independent independent-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5#linux linux-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-definitions-5 oval-definitions-schema.xsd http://oval.mitre.org/XMLSchema/oval-common-5 oval-common-schema.xsd">
+ <generator>
+ <oval:schema_version>5.11.1</oval:schema_version>
+ <oval:timestamp>0001-01-01T00:00:00+00:00</oval:timestamp>
+ </generator>
+
+ <definitions>
+ <definition class="compliance" version="1" id="oval:x:def:1">
+ <metadata>
+ <title>The regular expression and the provided file should exceed recursion limits within pcre_exec used in the probe and cause a segfault.</title>
+ <description>x</description>
+ <affected family="unix">
+ <platform>x</platform>
+ </affected>
+ </metadata>
+ <criteria>
+ <criterion test_ref="oval:x:tst:1" comment="always pass"/>
+ </criteria>
+ </definition>
+ </definitions>
+
+ <tests>
+ <ind:textfilecontent54_test id="oval:x:tst:1" version="1" comment="Match 3 audit rules" check="all">
+ <ind:object object_ref="oval:x:obj:1"/>
+ </ind:textfilecontent54_test>
+ </tests>
+
+ <objects>
+ <ind:textfilecontent54_object id="oval:x:obj:1" version="1" comment="Object representing file">
+ <ind:path>/tmp</ind:path>
+ <ind:filename>30-ospp-v42.rules</ind:filename>
+ <ind:pattern operation="pattern match">-a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-create(?:[^.]|\.\s)*-a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-modification(?:[^.]|\.\s)*-a always,exit -F arch=b32 -S open,creat,truncate,ftruncate,openat,open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccesful-access</ind:pattern>
+ <ind:instance datatype="int" operation="greater than or equal">1</ind:instance>
+ </ind:textfilecontent54_object>
+ </objects>
+
+</oval_definitions>
diff --git a/tests/probes/textfilecontent54/test_recursion_limit.sh b/tests/probes/textfilecontent54/test_recursion_limit.sh
new file mode 100755
index 000000000..2619dafdd
--- /dev/null
+++ b/tests/probes/textfilecontent54/test_recursion_limit.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+set -e -o pipefail
+
+. $builddir/tests/test_common.sh
+
+probecheck "textfilecontent54" || exit 255
+
+cp $srcdir/30-ospp-v42.rules /tmp
+
+name=$(basename $0 .sh)
+input=$srcdir/$name.oval.xml
+result=$(mktemp)
+stdout=$(mktemp)
+stderr=$(mktemp)
+
+$OSCAP oval eval --results $result $input > $stdout 2> $stderr
+
+grep -q "Function pcre_exec() failed to match a regular expression with return code -21" $stderr
+
+assert_exists 1 '/oval_results/results/system/definitions/definition[@definition_id="oval:x:def:1" and @result="error"]'
+
+co='/oval_results/results/system/oval_system_characteristics/collected_objects'
+assert_exists 1 $co'/object[@flag="error"]'
+assert_exists 1 $co'/object/message[@level="error"]'
+assert_exists 1 $co'/object/message[text()="Regular expression pattern match failed in file /tmp/30-ospp-v42.rules with error -21."]'
+
+rm -f /tmp/30-ospp-v42.rules
+rm -f $result
+rm -f $stdout
+rm -f $stderr