Blob Blame History Raw
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&amp;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&amp;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