From a71cca137eb33f659354ce0ebda4951cb26485df Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Mon, 6 Nov 2023 22:43:59 +0100
Subject: [PATCH] core: convert project to PCRE2

Convert project to PCRE2 as PCRE is EOL and won't receive any security
updates anymore.

PCRE2 changed some API and concept. Mainly there isn't the concept of
study anymore, replaced by match_context concept and match_data is used
instead of ovector to handle results. Because of this the scratcher is
not needed anymore and is replaced by a simple function to setup the max
ovector size on end module init.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 README.md                                     |  17 +-
 cmake/FindPCRE.cmake                          |  32 --
 cmake/FindPCRE2.cmake                         |  32 ++
 cmake/create_pkg_config.cmake                 |   4 +-
 cmake/include_libraries.cmake                 |   2 +-
 configure_cmake.sh                            |  16 +-
 lua/balanced.lua                              |   2 +-
 lua/max_detect.lua                            |   6 +-
 lua/security.lua                              |   4 +-
 snort.pc.in                                   |   4 +-
 src/CMakeLists.txt                            |   4 +-
 src/detection/detection_module.cc             |  48 +--
 src/detection/detection_options.cc            |   6 +-
 src/ips_options/ips_options.cc                |   4 +-
 src/ips_options/ips_pcre.cc                   | 391 ++++++++----------
 src/main/shell.cc                             |   9 +-
 src/main/snort_config.h                       |  26 +-
 .../appid/lua_detector_api.cc                 |  62 +--
 src/parser/parse_rule.cc                      |   4 +-
 src/parser/parse_stream.cc                    |   2 +-
 src/search_engines/test/hyperscan_test.cc     |   2 +-
 src/utils/stats.cc                            |   6 +-
 src/utils/stats.h                             |   6 +-
 src/utils/util.cc                             |   8 +-
 tools/snort2lua/config_states/config_api.cc   |  12 +-
 .../config_states/config_no_option.cc         |  14 +-
 .../config_states/config_one_int_option.cc    |  24 +-
 tools/snort2lua/rule_states/CMakeLists.txt    |   2 +-
 tools/snort2lua/rule_states/rule_api.cc       |   4 +-
 .../{rule_pcre.cc => rule_pcre2.cc}           |  40 +-
 .../snort2lua/rule_states/rule_sd_pattern.cc  |   4 +-
 31 files changed, 393 insertions(+), 404 deletions(-)
 delete mode 100644 cmake/FindPCRE.cmake
 create mode 100644 cmake/FindPCRE2.cmake
 rename tools/snort2lua/rule_states/{rule_pcre.cc => rule_pcre2.cc} (80%)

--- a/README.md
+++ b/README.md
@@ -8,13 +8,14 @@ topics:
 
 ---
 
-* [Overview](#overview)
-* [Dependencies](#dependencies)
-* [Download](#download)
-* [Build Snort](#build-snort)
-* [Run Snort](#run-snort)
-* [Documentation](#documentation)
-* [Squeal](#squeal)
+- [Snort++](#snort)
+- [OVERVIEW](#overview)
+- [DEPENDENCIES](#dependencies)
+- [DOWNLOAD](#download)
+- [BUILD SNORT](#build-snort)
+- [RUN SNORT](#run-snort)
+- [DOCUMENTATION](#documentation)
+- [SQUEAL](#squeal)
 
 # OVERVIEW
 
@@ -61,7 +62,7 @@ the latest:
 * OpenSSL from https://www.openssl.org/source/ for SHA and MD5 file signatures,
   the protected_content rule option, and SSL service detection
 * pcap from http://www.tcpdump.org for tcpdump style logging
-* pcre from http://www.pcre.org for regular expression pattern matching
+* pcre2 from http://www.pcre.org for regular expression pattern matching
 * pkgconfig from https://www.freedesktop.org/wiki/Software/pkg-config/ to locate build dependencies
 * zlib from http://www.zlib.net for decompression
 
--- a/cmake/FindPCRE.cmake
+++ /dev/null
@@ -1,32 +0,0 @@
-# - Find pcre
-# Find the native PCRE includes and library
-#
-#  PCRE_INCLUDE_DIR - where to find pcre.h, etc.
-#  PCRE_LIBRARIES    - List of libraries when using pcre.
-#  PCRE_FOUND        - True if pcre found.
-
-set(ERROR_MESSAGE
-    "\n\tERROR!  Libpcre library not found.
-    \tGet it from http://www.pcre.org\n"
-)
-
-find_package(PkgConfig)
-pkg_check_modules(PC_PCRE libpcre)
-
-# Use PCRE_INCLUDE_DIR_HINT and PCRE_LIBRARIES_DIR_HINT from configure_cmake.sh as primary hints
-# and then package config information after that.
-find_path(PCRE_INCLUDE_DIR pcre.h
-    HINTS ${PCRE_INCLUDE_DIR_HINT} ${PC_PCRE_INCLUDEDIR} ${PC_PCRE_INCLUDE_DIRS})
-find_library(PCRE_LIBRARIES NAMES pcre
-    HINTS ${PCRE_LIBRARIES_DIR_HINT} ${PC_PCRE_LIBDIR} ${PC_PCRE_LIBRARY_DIRS})
-
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(PCRE
-    REQUIRED_VARS PCRE_INCLUDE_DIR PCRE_LIBRARIES
-    FAIL_MESSAGE "${ERROR_MESSAGE}"
-)
-
-mark_as_advanced(
-    PCRE_LIBRARIES
-    PCRE_INCLUDE_DIR
-)
--- /dev/null
+++ b/cmake/FindPCRE2.cmake
@@ -0,0 +1,32 @@
+# - Find pcre2
+# Find the native PCRE2 includes and library
+#
+#  PCRE2_INCLUDE_DIR - where to find pcre2.h, etc.
+#  PCRE2_LIBRARIES    - List of libraries when using pcre2.
+#  PCRE2_FOUND        - True if pcre2 found.
+
+set(ERROR_MESSAGE
+    "\n\tERROR!  Libpcre2 library not found.
+    \tGet it from http://www.pcre.org\n"
+)
+
+find_package(PkgConfig)
+pkg_check_modules(PC_PCRE2 libpcre2-8)
+
+# Use PCRE2_INCLUDE_DIR_HINT and PCRE_LIBRARIES_DIR_HINT from configure_cmake.sh as primary hints
+# and then package config information after that.
+find_path(PCRE2_INCLUDE_DIR pcre2.h
+    HINTS ${PCRE2_INCLUDE_DIR_HINT} ${PC_PCRE2_INCLUDEDIR} ${PC_PCRE2_INCLUDE_DIRS})
+find_library(PCRE2_LIBRARIES NAMES pcre2-8
+    HINTS ${PCRE2_LIBRARIES_DIR_HINT} ${PC_PCRE2_LIBDIR} ${PC_PCRE2_LIBRARY_DIRS})
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(PCRE2-8
+    REQUIRED_VARS PCRE2_INCLUDE_DIR PCRE2_LIBRARIES
+    FAIL_MESSAGE "${ERROR_MESSAGE}"
+)
+
+mark_as_advanced(
+    PCRE2_LIBRARIES
+    PCRE2_INCLUDE_DIR
+)
--- a/cmake/create_pkg_config.cmake
+++ b/cmake/create_pkg_config.cmake
@@ -72,8 +72,8 @@ if(PCAP_INCLUDE_DIR)
     set(PCAP_CPPFLAGS "-I${PCAP_INCLUDE_DIR}")
 endif()
 
-if(PCRE_INCLUDE_DIR)
-    set(PCRE_CPPFLAGS "-I${PCRE_INCLUDE_DIR}")
+if(PCRE2_INCLUDE_DIR)
+    set(PCRE2_CPPFLAGS "-I${PCRE2_INCLUDE_DIR}")
 endif()
 
 if(UUID_INCLUDE_DIR)
--- a/cmake/include_libraries.cmake
+++ b/cmake/include_libraries.cmake
@@ -8,7 +8,7 @@ find_package(HWLOC REQUIRED)
 find_package(LuaJIT REQUIRED)
 find_package(OpenSSL 1.1.1 REQUIRED)
 find_package(PCAP REQUIRED)
-find_package(PCRE REQUIRED)
+find_package(PCRE2 REQUIRED)
 find_package(ZLIB REQUIRED)
 if (ENABLE_UNIT_TESTS)
     find_package(CppUTest REQUIRED)
--- a/configure_cmake.sh
+++ b/configure_cmake.sh
@@ -90,10 +90,10 @@ Optional Packages:
                             luajit include directory
     --with-luajit-libraries=DIR
                             luajit library directory
-    --with-pcre-includes=DIR
-                            libpcre include directory
-    --with-pcre-libraries=DIR
-                            libpcre library directory
+    --with-pcre2-includes=DIR
+                            libpcre2 include directory
+    --with-pcre2-libraries=DIR
+                            libpcre2 library directory
     --with-dnet-includes=DIR
                             libdnet include directory
     --with-dnet-libraries=DIR
@@ -417,11 +417,11 @@ while [ $# -ne 0 ]; do
         --with-luajit-libraries=*)
             append_cache_entry LUAJIT_LIBRARIES_DIR_HINT PATH $optarg
             ;;
-        --with-pcre-includes=*)
-            append_cache_entry PCRE_INCLUDE_DIR_HINT PATH $optarg
+        --with-pcre2-includes=*)
+            append_cache_entry PCRE2_INCLUDE_DIR_HINT PATH $optarg
             ;;
-        --with-pcre-libraries=*)
-            append_cache_entry PCRE_LIBRARIES_DIR_HINT PATH $optarg
+        --with-pcre2-libraries=*)
+            append_cache_entry PCRE2_LIBRARIES_DIR_HINT PATH $optarg
             ;;
         --with-dnet-includes=*)
             append_cache_entry DNET_INCLUDE_DIR_HINT PATH $optarg
--- a/lua/balanced.lua
+++ b/lua/balanced.lua
@@ -5,7 +5,7 @@
 
 arp_spoof = nil
 
-detection = { pcre_override = false }
+detection = { pcre2_override = false }
 
 http_inspect.request_depth = 300
 http_inspect.response_depth = 500
--- a/lua/max_detect.lua
+++ b/lua/max_detect.lua
@@ -10,13 +10,13 @@ ftp_server.check_encrypted = true
 
 detection =
 {
-    pcre_match_limit = 3500,
-    pcre_match_limit_recursion = 3500,
+    pcre2_match_limit = 3500,
+    pcre2_match_limit_recursion = 3500,
 
     -- enable for hyperscan for best throughput
     -- use multiple packet threads for fast startup
     --hyperscan_literals = true,
-    --pcre_to_regex = true
+    --pcre2_to_regex = true
 }
 
 http_inspect.decompress_pdf = true
--- a/lua/security.lua
+++ b/lua/security.lua
@@ -9,8 +9,8 @@ ftp_server.check_encrypted = true
 
 detection =
 {
-    pcre_match_limit = 3500,
-    pcre_match_limit_recursion = 3500
+    pcre2_match_limit = 3500,
+    pcre2_match_limit_recursion = 3500
 }
 
 http_inspect.decompress_pdf = true
--- a/snort.pc.in
+++ b/snort.pc.in
@@ -9,7 +9,7 @@ mandir=@mandir@
 infodir=@infodir@
 
 cpp_opts=DAQ LUAJIT
-cpp_opts_other=DNET HWLOC HYPERSCAN LZMA OPENSSL PCAP PCRE UUID
+cpp_opts_other=DNET HWLOC HYPERSCAN LZMA OPENSSL PCAP PCRE2 UUID
 
 PCAP_CPPFLAGS=@PCAP_CPPFLAGS@
 LUAJIT_CPPFLAGS=@LUAJIT_CPPFLAGS@
@@ -18,7 +18,7 @@ DAQ_CPPFLAGS=@DAQ_CPPFLAGS@
 FLEX_CPPFLAGS=@FLEX_CPPFLAGS@
 OPENSSL_CPPFLAGS=@OPENSSL_CPPFLAGS@
 HWLOC_CPPFLAGS=@HWLOC_CPPFLAGS@
-PCRE_CPPFLAGS=@PCRE_CPPFLAGS@
+PCRE2_CPPFLAGS=@PCRE2_CPPFLAGS@
 LZMA_CPPFLAGS=@LZMA_CPPFLAGS@
 HYPERSCAN_CPPFLAGS=@HYPERSCAN_CPPFLAGS@
 UUID_CPPFLAGS=@UUID_CPPFLAGS@
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -10,7 +10,7 @@ set(EXTERNAL_LIBRARIES
     ${LUAJIT_LIBRARIES}
     ${OPENSSL_CRYPTO_LIBRARY}
     ${PCAP_LIBRARIES}
-    ${PCRE_LIBRARIES}
+    ${PCRE2_LIBRARIES}
     ${ZLIB_LIBRARIES}
 )
 
@@ -21,7 +21,7 @@ set(EXTERNAL_INCLUDES
     ${HWLOC_INCLUDE_DIRS}
     ${OPENSSL_INCLUDE_DIR}
     ${PCAP_INCLUDE_DIR}
-    ${PCRE_INCLUDE_DIR}
+    ${PCRE2_INCLUDE_DIR}
     ${ZLIB_INCLUDE_DIRS}
 )
 
--- a/src/detection/detection_module.cc
+++ b/src/detection/detection_module.cc
@@ -96,21 +96,21 @@ static const Parameter detection_params[
     { "offload_threads", Parameter::PT_INT, "0:max32", "0",
       "maximum number of simultaneous offloads (defaults to disabled)" },
 
-    { "pcre_enable", Parameter::PT_BOOL, nullptr, "true",
-      "enable pcre pattern matching" },
+    { "pcre2_enable", Parameter::PT_BOOL, nullptr, "true",
+      "enable pcre2 pattern matching" },
 
-    { "pcre_match_limit", Parameter::PT_INT, "0:max32", "1500",
-      "limit pcre backtracking, 0 = off" },
+    { "pcre2_match_limit", Parameter::PT_INT, "0:max32", "1500",
+      "limit pcre2 backtracking, 0 = off" },
 
-    { "pcre_match_limit_recursion", Parameter::PT_INT, "0:max32", "1500",
-      "limit pcre stack consumption, 0 = off" },
+    { "pcre2_match_limit_recursion", Parameter::PT_INT, "0:max32", "1500",
+      "limit pcre2 stack consumption, 0 = off" },
 
-    { "pcre_override", Parameter::PT_BOOL, nullptr, "true",
-      "enable pcre match limit overrides when pattern matching (ie ignore /O)" },
+    { "pcre2_override", Parameter::PT_BOOL, nullptr, "true",
+      "enable pcre2 match limit overrides when pattern matching (ie ignore /O)" },
 
 #ifdef HAVE_HYPERSCAN
-    { "pcre_to_regex", Parameter::PT_BOOL, nullptr, "false",
-      "enable the use of regex instead of pcre for compatible expressions" },
+    { "pcre2_to_regex", Parameter::PT_BOOL, nullptr, "false",
+      "enable the use of regex instead of pcre2 for compatible expressions" },
 #endif
 
     { "enable_address_anomaly_checks", Parameter::PT_BOOL, nullptr, "false",
@@ -221,13 +221,13 @@ bool DetectionModule::set(const char*, V
     else if ( v.is("offload_threads") )
         sc->offload_threads = v.get_uint32();
 
-    else if ( v.is("pcre_enable") )
-        v.update_mask(sc->run_flags, RUN_FLAG__NO_PCRE, true);
+    else if ( v.is("pcre2_enable") )
+        v.update_mask(sc->run_flags, RUN_FLAG__NO_PCRE2, true);
 
-    else if ( v.is("pcre_match_limit") )
-        sc->pcre_match_limit = v.get_uint32();
+    else if ( v.is("pcre2_match_limit") )
+        sc->pcre2_match_limit = v.get_uint32();
 
-    else if ( v.is("pcre_match_limit_recursion") )
+    else if ( v.is("pcre2_match_limit_recursion") )
     {
         // Cap the pcre recursion limit to not exceed the stack size.
         //
@@ -252,21 +252,21 @@ bool DetectionModule::set(const char*, V
         if (max_rec < 0)
             max_rec = 0;
 
-        sc->pcre_match_limit_recursion = v.get_uint32();
-        if (sc->pcre_match_limit_recursion > max_rec)
+        sc->pcre2_match_limit_recursion = v.get_uint32();
+        if (sc->pcre2_match_limit_recursion > max_rec)
         {
-            sc->pcre_match_limit_recursion = max_rec;
-            LogMessage("Capping pcre_match_limit_recursion to %ld, thread stack_size %ld.\n",
-                sc->pcre_match_limit_recursion, thread_stack_size);
+            sc->pcre2_match_limit_recursion = max_rec;
+            LogMessage("Capping pcre2_match_limit_recursion to %ld, thread stack_size %llu.\n",
+                sc->pcre2_match_limit_recursion, thread_stack_size);
         }
     }
 
-    else if ( v.is("pcre_override") )
-        sc->pcre_override = v.get_bool();
+    else if ( v.is("pcre2_override") )
+        sc->pcre2_override = v.get_bool();
 
 #ifdef HAVE_HYPERSCAN
-    else if ( v.is("pcre_to_regex") )
-        sc->pcre_to_regex = v.get_bool();
+    else if ( v.is("pcre2_to_regex") )
+        sc->pcre2_to_regex = v.get_bool();
 #endif
 
     else if ( v.is("enable_address_anomaly_checks") )
--- a/src/detection/detection_options.cc
+++ b/src/detection/detection_options.cc
@@ -595,7 +595,7 @@ int detection_option_node_evaluate(
                             {
                                 if ( !child_node->is_relative )
                                 {
-                                    // If it's a non-relative content or pcre, no reason
+                                    // If it's a non-relative content or pcre2, no reason
                                     // to check again.  Only increment result once.
                                     // Should hit this condition on first loop iteration.
                                     if ( loop_count == 1 )
@@ -661,10 +661,10 @@ int detection_option_node_evaluate(
                 }
 
                 // If all children branches matched, we don't need to reeval any of
-                // the children so don't need to reeval this content/pcre rule
+                // the children so don't need to reeval this content/pcre2 rule
                 // option at a new offset.
                 // Else, reset the DOE ptr to last eval for offset/depth,
-                // distance/within adjustments for this same content/pcre rule option.
+                // distance/within adjustments for this same content/pcre2 rule option.
                 // If the node and its sub-tree propagate MATCH back,
                 // then all its continuations are recalled.
                 if ( result == node->num_children )
--- a/src/ips_options/ips_options.cc
+++ b/src/ips_options/ips_options.cc
@@ -72,7 +72,7 @@ extern const BaseApi* ips_ip_proto[];
 extern const BaseApi* ips_isdataat[];
 extern const BaseApi* ips_itype[];
 extern const BaseApi* ips_msg[];
-extern const BaseApi* ips_pcre[];
+extern const BaseApi* ips_pcre2[];
 extern const BaseApi* ips_priority[];
 extern const BaseApi* ips_raw_data[];
 extern const BaseApi* ips_rem[];
@@ -146,7 +146,7 @@ void load_ips_options()
     PluginManager::load_plugins(ips_isdataat);
     PluginManager::load_plugins(ips_itype);
     PluginManager::load_plugins(ips_msg);
-    PluginManager::load_plugins(ips_pcre);
+    PluginManager::load_plugins(ips_pcre2);
     PluginManager::load_plugins(ips_priority);
     PluginManager::load_plugins(ips_raw_data);
     PluginManager::load_plugins(ips_rem);
--- a/src/ips_options/ips_pcre.cc
+++ b/src/ips_options/ips_pcre.cc
@@ -23,7 +23,8 @@
 #include "config.h"
 #endif
 
-#include <pcre.h>
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>
 
 #include <cassert>
 
@@ -43,33 +44,31 @@
 
 using namespace snort;
 
-#ifndef PCRE_STUDY_JIT_COMPILE
-#define PCRE_STUDY_JIT_COMPILE 0
+#ifndef PCRE2_STUDY_JIT_COMPILE
+#define PCRE2_STUDY_JIT_COMPILE 0
 #endif
 
 //#define NO_JIT // uncomment to disable JIT for Xcode
 
 #ifdef NO_JIT
-#define PCRE_STUDY_FLAGS 0
-#define pcre_release(x) pcre_free(x)
+#define PCRE2_JIT 0
 #else
-#define PCRE_STUDY_FLAGS PCRE_STUDY_JIT_COMPILE
-#define pcre_release(x) pcre_free_study(x)
+#define PCRE2_JIT PCRE2_STUDY_JIT_COMPILE
 #endif
+#define pcre2_release(x) pcre2_code_free(x)
 
 #define SNORT_PCRE_RELATIVE         0x00010 // relative to the end of the last match
 #define SNORT_PCRE_INVERT           0x00020 // invert detect
 #define SNORT_PCRE_ANCHORED         0x00040
 #define SNORT_OVERRIDE_MATCH_LIMIT  0x00080 // Override default limits on match & match recursion
 
-#define s_name "pcre"
+#define s_name "pcre2"
 #define mod_regex_name "regex"
 
-struct PcreData
+struct Pcre2Data
 {
-    pcre* re;           /* compiled regex */
-    pcre_extra* pe;     /* studied regex foo */
-    bool free_pe;
+    pcre2_code* re;     /* compiled regex */
+    pcre2_match_context* match_context; /* match_context for limits */
     int options;        /* sp_pcre specific options (relative & inverse) */
     char* expression;
 };
@@ -83,36 +82,32 @@ struct PcreData
 // by verify; search uses the value in snort conf
 static int s_ovector_max = -1;
 
-static unsigned scratch_index;
-static ScratchAllocator* scratcher = nullptr;
-
-static THREAD_LOCAL ProfileStats pcrePerfStats;
+static THREAD_LOCAL ProfileStats pcre2PerfStats;
 
 //-------------------------------------------------------------------------
 // implementation foo
 //-------------------------------------------------------------------------
 
-static void pcre_capture(
-    const void* code, const void* extra)
+static void pcre2_capture(const void* code)
 {
     int tmp_ovector_size = 0;
 
-    pcre_fullinfo((const pcre*)code, (const pcre_extra*)extra,
-        PCRE_INFO_CAPTURECOUNT, &tmp_ovector_size);
+    pcre2_pattern_info((const pcre2_code *)code,
+        PCRE2_INFO_CAPTURECOUNT, &tmp_ovector_size);
 
     if (tmp_ovector_size > s_ovector_max)
         s_ovector_max = tmp_ovector_size;
 }
 
-static void pcre_check_anchored(PcreData* pcre_data)
+static void pcre2_check_anchored(Pcre2Data* pcre2_data)
 {
     int rc;
     unsigned long int options = 0;
 
-    if ((pcre_data == nullptr) || (pcre_data->re == nullptr) || (pcre_data->pe == nullptr))
+    if ((pcre2_data == nullptr) || (pcre2_data->re == nullptr))
         return;
 
-    rc = pcre_fullinfo(pcre_data->re, pcre_data->pe, PCRE_INFO_OPTIONS, (void*)&options);
+    rc = pcre2_pattern_info(pcre2_data->re, PCRE2_INFO_ARGOPTIONS, (void*)&options);
     switch (rc)
     {
     /* pcre_fullinfo fails for the following:
@@ -127,40 +122,41 @@ static void pcre_check_anchored(PcreData
         /* This is the success code */
         break;
 
-    case PCRE_ERROR_NULL:
-        ParseError("pcre_fullinfo: code and/or where were null.");
+    case PCRE2_ERROR_NULL:
+        ParseError("pcre2_fullinfo: code and/or where were null.");
         return;
 
-    case PCRE_ERROR_BADMAGIC:
-        ParseError("pcre_fullinfo: compiled code didn't have correct magic.");
+    case PCRE2_ERROR_BADMAGIC:
+        ParseError("pcre2_fullinfo: compiled code didn't have correct magic.");
         return;
 
-    case PCRE_ERROR_BADOPTION:
-        ParseError("pcre_fullinfo: option type is invalid.");
+    case PCRE2_ERROR_BADOPTION:
+        ParseError("pcre2_fullinfo: option type is invalid.");
         return;
 
     default:
-        ParseError("pcre_fullinfo: Unknown error code.");
+        ParseError("pcre2_fullinfo: Unknown error code.");
         return;
     }
 
-    if ((options & PCRE_ANCHORED) && !(options & PCRE_MULTILINE))
+    if ((options & PCRE2_ANCHORED) && !(options & PCRE2_MULTILINE))
     {
         /* This means that this pcre rule option shouldn't be EvalStatus
          * even if any of it's relative children should fail to match.
          * It is anchored to the cursor set by the previous cursor setting
          * rule option */
-        pcre_data->options |= SNORT_PCRE_ANCHORED;
+        pcre2_data->options |= SNORT_PCRE_ANCHORED;
     }
 }
 
-static void pcre_parse(const SnortConfig* sc, const char* data, PcreData* pcre_data)
+static void pcre2_parse(const SnortConfig* sc, const char* data, Pcre2Data* pcre2_data)
 {
-    const char* error;
+    PCRE2_UCHAR error[128];
     char* re, * free_me;
     char* opts;
     char delimit = '/';
-    int erroffset;
+    int errorcode;
+    PCRE2_SIZE erroffset;
     int compile_flags = 0;
 
     if (data == nullptr)
@@ -180,7 +176,7 @@ static void pcre_parse(const SnortConfig
 
     if (*re == '!')
     {
-        pcre_data->options |= SNORT_PCRE_INVERT;
+        pcre2_data->options |= SNORT_PCRE_INVERT;
         re++;
         while (isspace((int)*re))
             re++;
@@ -212,7 +208,7 @@ static void pcre_parse(const SnortConfig
     else if (*re != delimit)
         goto syntax;
 
-    pcre_data->expression = snort_strdup(re);
+    pcre2_data->expression = snort_strdup(re);
 
     /* find ending delimiter, trim delimit chars */
     opts = strrchr(re, delimit);
@@ -230,25 +226,25 @@ static void pcre_parse(const SnortConfig
     {
         switch (*opts)
         {
-        case 'i':  compile_flags |= PCRE_CASELESS;            break;
-        case 's':  compile_flags |= PCRE_DOTALL;              break;
-        case 'm':  compile_flags |= PCRE_MULTILINE;           break;
-        case 'x':  compile_flags |= PCRE_EXTENDED;            break;
+        case 'i':  compile_flags |= PCRE2_CASELESS;            break;
+        case 's':  compile_flags |= PCRE2_DOTALL;              break;
+        case 'm':  compile_flags |= PCRE2_MULTILINE;           break;
+        case 'x':  compile_flags |= PCRE2_EXTENDED;            break;
 
         /*
          * these are pcre specific... don't work with perl
          */
-        case 'A':  compile_flags |= PCRE_ANCHORED;            break;
-        case 'E':  compile_flags |= PCRE_DOLLAR_ENDONLY;      break;
-        case 'G':  compile_flags |= PCRE_UNGREEDY;            break;
+        case 'A':  compile_flags |= PCRE2_ANCHORED;            break;
+        case 'E':  compile_flags |= PCRE2_DOLLAR_ENDONLY;      break;
+        case 'G':  compile_flags |= PCRE2_UNGREEDY;            break;
 
         /*
-         * these are snort specific don't work with pcre or perl
+         * these are snort specific don't work with pcre2 or perl
          */
-        case 'R':  pcre_data->options |= SNORT_PCRE_RELATIVE; break;
+        case 'R':  pcre2_data->options |= SNORT_PCRE_RELATIVE; break;
         case 'O':
-            if ( sc->pcre_override )
-                pcre_data->options |= SNORT_OVERRIDE_MATCH_LIMIT;
+            if ( sc->pcre2_override )
+                pcre2_data->options |= SNORT_OVERRIDE_MATCH_LIMIT;
             break;
 
         default:
@@ -259,71 +255,68 @@ static void pcre_parse(const SnortConfig
     }
 
     /* now compile the re */
-    pcre_data->re = pcre_compile(re, compile_flags, &error, &erroffset, nullptr);
+    pcre2_data->re = pcre2_compile((PCRE2_SPTR)re, PCRE2_ZERO_TERMINATED, compile_flags, &errorcode, &erroffset, nullptr);
+
+    if (pcre2_data->re == nullptr)
+    {
+        pcre2_get_error_message(errorcode, error, 128);
+        ParseError(": pcre2 compile of '%s' failed at offset "
+            "%zu : %s", re, erroffset, error);
+        return;
+    }
 
-    if (pcre_data->re == nullptr)
+    /* now create match context */
+    pcre2_data->match_context = pcre2_match_context_create(NULL);
+    if(pcre2_data->match_context == NULL)
     {
-        ParseError(": pcre compile of '%s' failed at offset "
-            "%d : %s", re, erroffset, error);
+        ParseError(": failed to allocate memory for match context");
         return;
     }
 
     /* now study it... */
-    pcre_data->pe = pcre_study(pcre_data->re, PCRE_STUDY_FLAGS, &error);
+    if (PCRE2_JIT)
+        errorcode = pcre2_jit_compile(pcre2_data->re, PCRE2_JIT_COMPLETE);
 
-    if (pcre_data->pe)
+    if (PCRE2_JIT || errorcode)
     {
-        if ((sc->get_pcre_match_limit() != 0) &&
-            !(pcre_data->options & SNORT_OVERRIDE_MATCH_LIMIT))
+        if ((sc->get_pcre2_match_limit() != 0) &&
+            !(pcre2_data->options & SNORT_OVERRIDE_MATCH_LIMIT))
         {
-            if ( !(pcre_data->pe->flags & PCRE_EXTRA_MATCH_LIMIT) )
-                pcre_data->pe->flags |= PCRE_EXTRA_MATCH_LIMIT;
-
-            pcre_data->pe->match_limit = sc->get_pcre_match_limit();
+            pcre2_set_match_limit(pcre2_data->match_context, sc->get_pcre2_match_limit());
         }
 
-        if ((sc->get_pcre_match_limit_recursion() != 0) &&
-            !(pcre_data->options & SNORT_OVERRIDE_MATCH_LIMIT))
+        if ((sc->get_pcre2_match_limit_recursion() != 0) &&
+            !(pcre2_data->options & SNORT_OVERRIDE_MATCH_LIMIT))
         {
-            if ( !(pcre_data->pe->flags & PCRE_EXTRA_MATCH_LIMIT_RECURSION) )
-                pcre_data->pe->flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION;
-
-            pcre_data->pe->match_limit_recursion =
-                sc->get_pcre_match_limit_recursion();
+            pcre2_set_match_limit(pcre2_data->match_context, sc->get_pcre2_match_limit_recursion());
         }
     }
     else
     {
-        if (!(pcre_data->options & SNORT_OVERRIDE_MATCH_LIMIT) &&
-            ((sc->get_pcre_match_limit() != 0) ||
-             (sc->get_pcre_match_limit_recursion() != 0)))
+        if (!(pcre2_data->options & SNORT_OVERRIDE_MATCH_LIMIT) &&
+            ((sc->get_pcre2_match_limit() != 0) ||
+             (sc->get_pcre2_match_limit_recursion() != 0)))
         {
-            pcre_data->pe = (pcre_extra*)snort_calloc(sizeof(pcre_extra));
-            pcre_data->free_pe = true;
-
-            if (sc->get_pcre_match_limit() != 0)
+            if (sc->get_pcre2_match_limit() != 0)
             {
-                pcre_data->pe->flags |= PCRE_EXTRA_MATCH_LIMIT;
-                pcre_data->pe->match_limit = sc->get_pcre_match_limit();
+                pcre2_set_match_limit(pcre2_data->match_context, sc->get_pcre2_match_limit());
             }
 
-            if (sc->get_pcre_match_limit_recursion() != 0)
+            if (sc->get_pcre2_match_limit_recursion() != 0)
             {
-                pcre_data->pe->flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION;
-                pcre_data->pe->match_limit_recursion =
-                    sc->get_pcre_match_limit_recursion();
+                pcre2_set_match_limit(pcre2_data->match_context, sc->get_pcre2_match_limit_recursion());
             }
         }
     }
 
-    if (error != nullptr)
+    if (PCRE2_JIT && errorcode)
     {
-        ParseError("pcre study failed : %s", error);
+        ParseError("pcre2 JIT failed : %s", error);
         return;
     }
 
-    pcre_capture(pcre_data->re, pcre_data->pe);
-    pcre_check_anchored(pcre_data);
+    pcre2_capture(pcre2_data->re);
+    pcre2_check_anchored(pcre2_data);
 
     snort_free(free_me);
     return;
@@ -332,40 +325,44 @@ syntax:
     snort_free(free_me);
 
     // ensure integrity from parse error to fatal error
-    if ( !pcre_data->expression )
-        pcre_data->expression = snort_strdup("");
+    if ( !pcre2_data->expression )
+        pcre2_data->expression = snort_strdup("");
 
-    ParseError("unable to parse pcre %s", data);
+    ParseError("unable to parse pcre2 %s", data);
 }
 
 /*
- * Perform a search of the PCRE data.
+ * Perform a search of the PCRE2 data.
  * found_offset will be set to -1 when the find is unsuccessful OR the routine is inverted
  */
-static bool pcre_search(
+static bool pcre2_search(
     Packet* p,
-    const PcreData* pcre_data,
+    const Pcre2Data* pcre2_data,
     const uint8_t* buf,
     unsigned len,
     unsigned start_offset,
     int& found_offset)
 {
+    pcre2_match_data *match_data;
+    PCRE2_SIZE *ovector;
     bool matched;
 
     found_offset = -1;
 
-    std::vector<void *> ss = p->context->conf->state[get_instance_id()];
-    assert(ss[scratch_index]);
+    match_data = pcre2_match_data_create(p->context->conf->pcre2_ovector_size, NULL);
+    if (match_data == nullptr) {
+        pc.pcre2_error++;
+        return false;
+    }
 
-    int result = pcre_exec(
-        pcre_data->re,  /* result of pcre_compile() */
-        pcre_data->pe,  /* result of pcre_study()   */
-        (const char*)buf, /* the subject string */
-        len,            /* the length of the subject string */
-        start_offset,   /* start at offset 0 in the subject */
-        0,              /* options(handled at compile time */
-        (int*)ss[scratch_index], /* vector for substring information */
-        p->context->conf->pcre_ovector_size); /* number of elements in the vector */
+    int result = pcre2_match(
+        pcre2_data->re,  /* result of pcre_compile() */
+        (PCRE2_SPTR)buf, /* the subject string */
+        (PCRE2_SIZE)len, /* the length of the subject string */
+        (PCRE2_SIZE)start_offset, /* start at offset 0 in the subject */
+        0,               /* options(handled at compile time */
+        match_data,      /* match data to store the match results */
+        pcre2_data->match_context); /* match context for limits */
 
     if (result >= 0)
     {
@@ -390,34 +387,37 @@ static bool pcre_search(
          * and a single int for scratch space.
          */
 
-        found_offset = ((int*)ss[scratch_index])[1];
+        ovector = pcre2_get_ovector_pointer(match_data);
+        found_offset = ovector[1];
     }
-    else if (result == PCRE_ERROR_NOMATCH)
+    else if (result == PCRE2_ERROR_NOMATCH)
     {
         matched = false;
     }
-    else if (result == PCRE_ERROR_MATCHLIMIT)
+    else if (result == PCRE2_ERROR_MATCHLIMIT)
     {
-        pc.pcre_match_limit++;
+        pc.pcre2_match_limit++;
         matched = false;
     }
-    else if (result == PCRE_ERROR_RECURSIONLIMIT)
+    else if (result == PCRE2_ERROR_RECURSIONLIMIT)
     {
-        pc.pcre_recursion_limit++;
+        pc.pcre2_recursion_limit++;
         matched = false;
     }
     else
     {
-        pc.pcre_error++;
+        pc.pcre2_error++;
         return false;
     }
 
     /* invert sense of match */
-    if (pcre_data->options & SNORT_PCRE_INVERT)
+    if (pcre2_data->options & SNORT_PCRE_INVERT)
     {
         matched = !matched;
     }
 
+    pcre2_match_data_free(match_data);
+
     return matched;
 }
 
@@ -425,14 +425,14 @@ static bool pcre_search(
 // class methods
 //-------------------------------------------------------------------------
 
-class PcreOption : public IpsOption
+class Pcre2Option : public IpsOption
 {
 public:
-    PcreOption(PcreData* c) :
+    Pcre2Option(Pcre2Data* c) :
         IpsOption(s_name, RULE_OPTION_TYPE_CONTENT)
     { config = c; }
 
-    ~PcreOption() override;
+    ~Pcre2Option() override;
 
     uint32_t hash() const override;
     bool operator==(const IpsOption&) const override;
@@ -446,17 +446,17 @@ public:
     EvalStatus eval(Cursor&, Packet*) override;
     bool retry(Cursor&, const Cursor&) override;
 
-    PcreData* get_data()
+    Pcre2Data* get_data()
     { return config; }
 
-    void set_data(PcreData* pcre)
+    void set_data(Pcre2Data* pcre)
     { config = pcre; }
 
 private:
-    PcreData* config;
+    Pcre2Data* config;
 };
 
-PcreOption::~PcreOption()
+Pcre2Option::~Pcre2Option()
 {
     if ( !config )
         return;
@@ -464,21 +464,16 @@ PcreOption::~PcreOption()
     if ( config->expression )
         snort_free(config->expression);
 
-    if ( config->pe )
-    {
-        if ( config->free_pe )
-            snort_free(config->pe);
-        else
-            pcre_release(config->pe);
-    }
+    if ( config->match_context )
+        pcre2_match_context_free(config->match_context);
 
     if ( config->re )
-        free(config->re);  // external allocation
+        pcre2_code_free(config->re);  // external allocation
 
     snort_free(config);
 }
 
-uint32_t PcreOption::hash() const
+uint32_t Pcre2Option::hash() const
 {
     uint32_t a = 0, b = 0, c = 0;
     int expression_len = strlen(config->expression);
@@ -532,14 +527,14 @@ uint32_t PcreOption::hash() const
     return c;
 }
 
-bool PcreOption::operator==(const IpsOption& ips) const
+bool Pcre2Option::operator==(const IpsOption& ips) const
 {
     if ( !IpsOption::operator==(ips) )
         return false;
 
-    const PcreOption& rhs = (const PcreOption&)ips;
-    PcreData* left = config;
-    PcreData* right = rhs.config;
+    const Pcre2Option& rhs = (const Pcre2Option&)ips;
+    Pcre2Data* left = config;
+    Pcre2Data* right = rhs.config;
 
     if (( strcmp(left->expression, right->expression) == 0) &&
         ( left->options == right->options))
@@ -550,13 +545,13 @@ bool PcreOption::operator==(const IpsOpt
     return false;
 }
 
-IpsOption::EvalStatus PcreOption::eval(Cursor& c, Packet* p)
+IpsOption::EvalStatus Pcre2Option::eval(Cursor& c, Packet* p)
 {
     // cppcheck-suppress unreadVariable
-    RuleProfile profile(pcrePerfStats);
+    RuleProfile profile(pcre2PerfStats);
 
-    // short circuit this for testing pcre performance impact
-    if ( p->context->conf->no_pcre() )
+    // short circuit this for testing pcre2 performance impact
+    if ( p->context->conf->no_pcre2() )
         return NO_MATCH;
 
     unsigned pos = c.get_delta();
@@ -570,7 +565,7 @@ IpsOption::EvalStatus PcreOption::eval(C
 
     int found_offset = -1; // where is the ending location of the pattern
 
-    if ( pcre_search(p, config, c.buffer()+adj, c.size()-adj, pos, found_offset) )
+    if ( pcre2_search(p, config, c.buffer()+adj, c.size()-adj, pos, found_offset) )
     {
         if ( found_offset > 0 )
         {
@@ -585,17 +580,17 @@ IpsOption::EvalStatus PcreOption::eval(C
 }
 
 // we always advance by found_offset so no adjustments to cursor are done
-// here; note also that this means relative pcre matches on overlapping
+// here; note also that this means relative pcre2 matches on overlapping
 // patterns won't work.  given the test pattern "ABABACD":
 //
 // ( sid:1; content:"ABA"; content:"C"; within:1; )
-// ( sid:2; pcre:"/ABA/"; content:"C"; within:1; )
+// ( sid:2; pcre2:"/ABA/"; content:"C"; within:1; )
 //
 // sid 1 will fire but sid 2 will NOT.  this example is easily fixed by
-// using content, but more advanced pcre won't work for the relative /
+// using content, but more advanced pcre2 won't work for the relative /
 // overlap case.
 
-bool PcreOption::retry(Cursor&, const Cursor&)
+bool Pcre2Option::retry(Cursor&, const Cursor&)
 {
     if ((config->options & (SNORT_PCRE_INVERT | SNORT_PCRE_ANCHORED)))
     {
@@ -616,46 +611,43 @@ static const Parameter s_params[] =
     { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
 };
 
-struct PcreStats
+struct Pcre2Stats
 {
-    PegCount pcre_rules;
+    PegCount pcre2_rules;
 #ifdef HAVE_HYPERSCAN
-    PegCount pcre_to_hyper;
+    PegCount pcre2_to_hyper;
 #endif
-    PegCount pcre_native;
-    PegCount pcre_negated;
+    PegCount pcre2_native;
+    PegCount pcre2_negated;
 };
 
 const PegInfo pcre_pegs[] =
 {
-    { CountType::SUM, "pcre_rules", "total rules processed with pcre option" },
+    { CountType::SUM, "pcre2_rules", "total rules processed with pcre2 option" },
 #ifdef HAVE_HYPERSCAN
-    { CountType::SUM, "pcre_to_hyper", "total pcre rules by hyperscan engine" },
+    { CountType::SUM, "pcre2_to_hyper", "total pcre2 rules by hyperscan engine" },
 #endif
-    { CountType::SUM, "pcre_native", "total pcre rules compiled by pcre engine" },
-    { CountType::SUM, "pcre_negated", "total pcre rules using negation syntax" },
+    { CountType::SUM, "pcre2_native", "total pcre2 rules compiled by pcre engine" },
+    { CountType::SUM, "pcre2_negated", "total pcre2 rules using negation syntax" },
     { CountType::END, nullptr, nullptr }
 };
 
-PcreStats pcre_stats;
+Pcre2Stats pcre2_stats;
 
 #define s_help \
-    "rule option for matching payload data with pcre"
+    "rule option for matching payload data with pcre2"
 
-class PcreModule : public Module
+class Pcre2Module : public Module
 {
 public:
-    PcreModule() : Module(s_name, s_help, s_params)
+    Pcre2Module() : Module(s_name, s_help, s_params)
     {
         data = nullptr;
-        scratcher = new SimpleScratchAllocator(scratch_setup, scratch_cleanup);
-        scratch_index = scratcher->get_id();
     }
 
-    ~PcreModule() override
+    ~Pcre2Module() override
     {
         delete data;
-        delete scratcher;
     }
 
 #ifdef HAVE_HYPERSCAN
@@ -665,12 +657,12 @@ public:
     bool end(const char*, int, SnortConfig*) override;
 
     ProfileStats* get_profile() const override
-    { return &pcrePerfStats; }
+    { return &pcre2PerfStats; }
 
     const PegInfo* get_pegs() const override;
     PegCount* get_counts() const override;
 
-    PcreData* get_data();
+    Pcre2Data* get_data();
 
     bool global_stats() const override
     { return true; }
@@ -682,31 +674,28 @@ public:
     { return mod_regex; }
 
 private:
-    PcreData* data;
+    Pcre2Data* data;
     Module* mod_regex = nullptr;
     std::string re;
-
-    static bool scratch_setup(SnortConfig*);
-    static void scratch_cleanup(SnortConfig*);
 };
 
-PcreData* PcreModule::get_data()
+Pcre2Data* Pcre2Module::get_data()
 {
-    PcreData* tmp = data;
+    Pcre2Data* tmp = data;
     data = nullptr;
     return tmp;
 }
 
-const PegInfo* PcreModule::get_pegs() const
+const PegInfo* Pcre2Module::get_pegs() const
 { return pcre_pegs; }
 
-PegCount* PcreModule::get_counts() const
-{ return (PegCount*)&pcre_stats; }
+PegCount* Pcre2Module::get_counts() const
+{ return (PegCount*)&pcre2_stats; }
 
 #ifdef HAVE_HYPERSCAN
-bool PcreModule::begin(const char* name, int v, SnortConfig* sc)
+bool Pcre2Module::begin(const char* name, int v, SnortConfig* sc)
 {
-    if ( sc->pcre_to_regex )
+    if ( sc->pcre2_to_regex )
     {
         if ( !mod_regex )
             mod_regex = ModuleManager::get_module(mod_regex_name);
@@ -718,7 +707,7 @@ bool PcreModule::begin(const char* name,
 }
 #endif
 
-bool PcreModule::set(const char* name, Value& v, SnortConfig* sc)
+bool Pcre2Module::set(const char* name, Value& v, SnortConfig* sc)
 {
     assert(v.is("~re"));
     re = v.get_string();
@@ -729,50 +718,28 @@ bool PcreModule::set(const char* name, V
     return true;
 }
 
-bool PcreModule::end(const char* name, int v, SnortConfig* sc)
+bool Pcre2Module::end(const char* name, int v, SnortConfig* sc)
 {
     if( mod_regex )
         mod_regex = mod_regex->end(name, v, sc) ? mod_regex : nullptr;
 
     if ( !mod_regex )
     {
-        data = (PcreData*)snort_calloc(sizeof(*data));
-        pcre_parse(sc, re.c_str(), data);
+        data = (Pcre2Data*)snort_calloc(sizeof(*data));
+        pcre2_parse(sc, re.c_str(), data);
     }
 
-    return true;
-}
-
-bool PcreModule::scratch_setup(SnortConfig* sc)
-{
-    if ( s_ovector_max < 0 )
-        return false;
-
     // The pcre_fullinfo() function can be used to find out how many
     // capturing subpatterns there are in a compiled pattern. The
     // smallest size for ovector that will allow for n captured
     // substrings, in addition to the offsets of the substring matched
     // by the whole pattern is 3(n+1).
-
-    sc->pcre_ovector_size = 3 * (s_ovector_max + 1);
-    s_ovector_max = -1;
-
-    for ( unsigned i = 0; i < sc->num_slots; ++i )
-    {
-        std::vector<void *>& ss = sc->state[i];
-        ss[scratch_index] = snort_calloc(sc->pcre_ovector_size, sizeof(int));
+    if ( s_ovector_max >= 0 ) {
+        sc->pcre2_ovector_size = 3 * (s_ovector_max + 1);
+        s_ovector_max = -1;
     }
-    return true;
-}
 
-void PcreModule::scratch_cleanup(SnortConfig* sc)
-{
-    for ( unsigned i = 0; i < sc->num_slots; ++i )
-    {
-        std::vector<void *>& ss = sc->state[i];
-        snort_free(ss[scratch_index]);
-        ss[scratch_index] = nullptr;
-    }
+    return true;
 }
 
 //-------------------------------------------------------------------------
@@ -780,21 +747,21 @@ void PcreModule::scratch_cleanup(SnortCo
 //-------------------------------------------------------------------------
 
 static Module* mod_ctor()
-{ return new PcreModule; }
+{ return new Pcre2Module; }
 
 static void mod_dtor(Module* m)
 { delete m; }
 
-static IpsOption* pcre_ctor(Module* p, OptTreeNode* otn)
+static IpsOption* pcre2_ctor(Module* p, OptTreeNode* otn)
 {
-    pcre_stats.pcre_rules++;
-    PcreModule* m = (PcreModule*)p;
+    pcre2_stats.pcre2_rules++;
+    Pcre2Module* m = (Pcre2Module*)p;
 
 #ifdef HAVE_HYPERSCAN
     Module* mod_regex = m->get_mod_regex();
     if ( mod_regex )
     {
-        pcre_stats.pcre_to_hyper++;
+        pcre2_stats.pcre2_to_hyper++;
         const IpsApi* opt_api = IpsManager::get_option_api(mod_regex_name);
         return opt_api->ctor(mod_regex, otn);
     }
@@ -803,16 +770,16 @@ static IpsOption* pcre_ctor(Module* p, O
     UNUSED(otn);
 #endif
     {
-        pcre_stats.pcre_native++;
-        PcreData* d = m->get_data();
-        return new PcreOption(d);
+        pcre2_stats.pcre2_native++;
+        Pcre2Data* d = m->get_data();
+        return new Pcre2Option(d);
     }
 }
 
-static void pcre_dtor(IpsOption* p)
+static void pcre2_dtor(IpsOption* p)
 { delete p; }
 
-static const IpsApi pcre_api =
+static const IpsApi pcre2_api =
 {
     {
         PT_IPS_OPTION,
@@ -832,17 +799,17 @@ static const IpsApi pcre_api =
     nullptr,
     nullptr,
     nullptr,
-    pcre_ctor,
-    pcre_dtor,
+    pcre2_ctor,
+    pcre2_dtor,
     nullptr
 };
 
 #ifdef BUILDING_SO
 SO_PUBLIC const BaseApi* snort_plugins[] =
 #else
-const BaseApi* ips_pcre[] =
+const BaseApi* ips_pcre2[] =
 #endif
 {
-    &pcre_api.base,
+    &pcre2_api.base,
     nullptr
 };
--- a/src/main/shell.cc
+++ b/src/main/shell.cc
@@ -29,7 +29,8 @@
 #include <fstream>
 #include <openssl/crypto.h>
 #include <pcap.h>
-#include <pcre.h>
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>
 #include <stdexcept>
 #include <vector>
 #include <zlib.h>
@@ -138,13 +139,17 @@ static void install_version_strings(lua_
 
 static void install_dependencies_strings(Shell* sh, lua_State* L)
 {
+
     assert(dep_versions[0]);
 
+    const char pcre2_version[32] = { 0 };
     std::vector<const char*> vs;
     const char* ljv = LUAJIT_VERSION;
     const char* osv = OpenSSL_version(SSLEAY_VERSION);
     const char* lpv = pcap_lib_version();
 
+    pcre2_config(PCRE2_CONFIG_VERSION, (PCRE2_UCHAR8 *)pcre2_version);
+
     while (*ljv and !isdigit(*ljv))
         ++ljv;
     while (*osv and !isdigit(*osv))
@@ -156,7 +161,7 @@ static void install_dependencies_strings
     vs.push_back(ljv);
     vs.push_back(osv);
     vs.push_back(lpv);
-    vs.push_back(pcre_version());
+    vs.push_back(pcre2_version);
     vs.push_back(zlib_version);
 #ifdef HAVE_HYPERSCAN
     vs.push_back(hs_version());
--- a/src/main/snort_config.h
+++ b/src/main/snort_config.h
@@ -60,7 +60,7 @@ enum RunFlag
     RUN_FLAG__PCAP_SHOW           = 0x00001000,
     RUN_FLAG__SHOW_FILE_CODES     = 0x00002000,
     RUN_FLAG__PAUSE               = 0x00004000,
-    RUN_FLAG__NO_PCRE             = 0x00008000,
+    RUN_FLAG__NO_PCRE2            = 0x00008000,
 
     RUN_FLAG__DUMP_RULE_STATE     = 0x00010000,
     RUN_FLAG__DUMP_RULE_DEPS      = 0x00020000,
@@ -214,13 +214,13 @@ public:
 
     //------------------------------------------------------
     // detection module stuff
-    // FIXIT-L pcre_match_limit* are interdependent
+    // FIXIT-L pcre2_match_limit* are interdependent
     // somehow a packet thread needs a much lower setting
-    long int pcre_match_limit = 1500;
-    long int pcre_match_limit_recursion = 1500;
+    long int pcre2_match_limit = 1500;
+    long int pcre2_match_limit_recursion = 1500;
 
-    int pcre_ovector_size = 0;
-    bool pcre_override = true;
+    int pcre2_ovector_size = 0;
+    bool pcre2_override = true;
 
     uint32_t run_flags = 0;
 
@@ -228,7 +228,7 @@ public:
     unsigned offload_threads = 0;    // disabled
 
     bool hyperscan_literals = false;
-    bool pcre_to_regex = false;
+    bool pcre2_to_regex = false;
 
     bool global_rule_state = false;
     bool global_default_rule_state = true;
@@ -600,8 +600,8 @@ public:
     bool alert_before_pass() const
     { return run_flags & RUN_FLAG__ALERT_BEFORE_PASS; }
 
-    bool no_pcre() const
-    { return run_flags & RUN_FLAG__NO_PCRE; }
+    bool no_pcre2() const
+    { return run_flags & RUN_FLAG__NO_PCRE2; }
 
     bool conf_error_out() const
     { return run_flags & RUN_FLAG__CONF_ERROR_OUT; }
@@ -616,11 +616,11 @@ public:
     uint8_t new_ttl() const
     { return get_network_policy()->new_ttl; }
 
-    long int get_pcre_match_limit() const
-    { return pcre_match_limit; }
+    long int get_pcre2_match_limit() const
+    { return pcre2_match_limit; }
 
-    long int get_pcre_match_limit_recursion() const
-    { return pcre_match_limit_recursion; }
+    long int get_pcre2_match_limit_recursion() const
+    { return pcre2_match_limit_recursion; }
 
     const ProfilerConfig* get_profiler() const
     { return profiler; }
--- a/src/network_inspectors/appid/lua_detector_api.cc
+++ b/src/network_inspectors/appid/lua_detector_api.cc
@@ -25,7 +25,8 @@
 
 #include "lua_detector_api.h"
 #include <lua.hpp>
-#include <pcre.h>
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>
 #include <unordered_map>
 
 #include "detection/fp_config.h"
@@ -714,7 +715,7 @@ static int detector_get_packet_direction
     return 1;
 }
 
-/**Perform a pcre match with grouping. A simple regular expression match with no grouping
+/**Perform a pcre2 match with grouping. A simple regular expression match with no grouping
  * can also be performed.
  *
  * @param Lua_State* - Lua state variable.
@@ -723,41 +724,50 @@ static int detector_get_packet_direction
  * @return matchedStrings/stack - matched strings are pushed on stack starting with group 0.
  *     There may be 0 or more strings.
  */
-static int detector_get_pcre_groups(lua_State* L)
+static int detector_get_pcre2_groups(lua_State* L)
 {
     auto& ud = *UserData<LuaObject>::check(L, DETECTOR, 1);
     // Verify detector user data and that we are in packet context
     LuaStateDescriptor* lsd = ud->validate_lua_state(true);
 
-    int ovector[OVECCOUNT];
-    const char* error;
-    int erroffset;
+    PCRE2_SIZE* ovector;
+    pcre2_match_data* match_data;
+    PCRE2_UCHAR error[128];
+    PCRE2_SIZE erroffset;
+    int errorcode;
 
     const char* pattern = lua_tostring(L, 2);
     unsigned int offset = lua_tonumber(L, 3);     /*offset can be zero, no check necessary. */
 
     /*compile the regular expression pattern, and handle errors */
-    pcre* re = pcre_compile(pattern,  // the pattern
-        PCRE_DOTALL,                  // default options - dot matches all inc \n
-        &error,                       // for error message
-        &erroffset,                   // for error offset
-        nullptr);                     // use default character tables
+    pcre2_code* re = pcre2_compile((PCRE2_SPTR)pattern,  // the pattern
+        PCRE2_ZERO_TERMINATED,         // assume zero terminated strings
+        PCRE2_DOTALL,                  // default options - dot matches all inc \n
+        &errorcode,                    // for error message
+        &erroffset,                    // for error offset
+        nullptr);                      // use default character tables
 
     if (re == nullptr)
     {
-        appid_log(lsd->ldp.pkt, TRACE_ERROR_LEVEL, "PCRE compilation failed at offset %d: %s\n", erroffset, error);
+        pcre2_get_error_message(errorcode, error, 128);
+        appid_log(lsd->ldp.pkt, TRACE_ERROR_LEVEL, "PCRE2 compilation failed at offset %d: %s\n", erroffset, error);
+        return 0;
+    }
+
+    match_data = pcre2_match_data_create(OVECCOUNT, NULL);
+    if (match_data == nullptr) {
+        appid_log(lsd->ldp.pkt, TRACE_ERROR_LEVEL, "PCRE2 failed to allocate mem for match_data\n");
         return 0;
     }
 
     /*pattern match against the subject string. */
-    int rc = pcre_exec(re,            // compiled pattern
-        nullptr,                      // no extra data
-        (const char*)lsd->ldp.data,   // subject string
-        lsd->ldp.size,                // length of the subject
-        offset,                       // offset 0
-        0,                            // default options
-        ovector,                      // output vector for substring information
-        OVECCOUNT);                   // number of elements in the output vector
+    int rc = pcre2_match(re,         // compiled pattern
+        (PCRE2_SPTR)lsd->ldp.data,   // subject string
+        (PCRE2_SIZE)lsd->ldp.size,   // length of the subject
+        (PCRE2_SIZE)offset,          // offset 0
+        0,                           // default options
+        match_data,                  // match data for match results
+        NULL);                       // no match context
 
     if (rc >= 0)
     {
@@ -771,10 +781,11 @@ static int detector_get_pcre_groups(lua_
         if (!lua_checkstack(L, rc))
         {
             appid_log(lsd->ldp.pkt, TRACE_WARNING_LEVEL, "Cannot grow Lua stack by %d slots to hold "
-                "PCRE matches\n", rc);
+                "PCRE2 matches\n", rc);
             return 0;
         }
 
+        ovector = pcre2_get_ovector_pointer(match_data);
         for (int i = 0; i < rc; i++)
         {
             lua_pushlstring(L, (const char*)lsd->ldp.data + ovector[2*i], ovector[2*i+1] -
@@ -784,12 +795,13 @@ static int detector_get_pcre_groups(lua_
     else
     {
         // log errors except no matches
-        if (rc != PCRE_ERROR_NOMATCH)
-            appid_log(lsd->ldp.pkt, TRACE_WARNING_LEVEL, "PCRE regular expression group match failed. rc: %d\n", rc);
+        if (rc != PCRE2_ERROR_NOMATCH)
+            appid_log(lsd->ldp.pkt, TRACE_WARNING_LEVEL, "PCRE2 regular expression group match failed. rc: %d\n", rc);
         rc = 0;
     }
 
-    pcre_free(re);
+    pcre2_match_data_free(match_data);
+    pcre2_code_free(re);
     return rc;
 }
 
@@ -3229,7 +3241,7 @@ static const luaL_Reg detector_methods[]
     { "getPacketSize",            detector_get_packet_size },
     { "getPacketDir",             detector_get_packet_direction },
     { "matchSimplePattern",       detector_memcmp },
-    { "getPcreGroups",            detector_get_pcre_groups },
+    { "getPcreGroups",            detector_get_pcre2_groups },
     { "getL4Protocol",            detector_get_protocol_type },
     { "getPktSrcAddr",            detector_get_packet_src_addr },
     { "getPktDstAddr",            detector_get_packet_dst_addr },
--- a/src/parser/parse_rule.cc
+++ b/src/parser/parse_rule.cc
@@ -911,10 +911,10 @@ void parse_rule_dir(SnortConfig*, const
         ParseError("illegal direction specifier: %s", s);
 }
 
-// Values of the rule options "pcre", "regex" and "sd_pattern" are already escaped
+// Values of the rule options "pcre2", "regex" and "sd_pattern" are already escaped
 // They are not unescaped during the rule parsing
 static bool is_already_escaped(const std::string& opt_key)
-{ return opt_key == "pcre" or opt_key == "regex" or opt_key == "sd_pattern"; }
+{ return opt_key == "pcre2" or opt_key == "regex" or opt_key == "sd_pattern"; }
 
 static std::string escape(const std::string& s)
 {
--- a/src/parser/parse_stream.cc
+++ b/src/parser/parse_stream.cc
@@ -603,7 +603,7 @@ static bool exec(
 // that individual rule options can do whatever
 static int get_escape(const string& s)
 {
-    if ( s == "pcre" )
+    if ( s == "pcre2" )
         return 0;  // no escape, option goes to ;
 
     else if ( s == "regex" || s == "sd_pattern" )
--- a/src/search_engines/test/hyperscan_test.cc
+++ b/src/search_engines/test/hyperscan_test.cc
@@ -223,7 +223,7 @@ TEST(mpse_hs_match, regex)
     CHECK(hits == 3);
 }
 
-TEST(mpse_hs_match, pcre)
+TEST(mpse_hs_match, pcre2)
 {
     Mpse::PatternDescriptor desc;
 
--- a/src/utils/stats.cc
+++ b/src/utils/stats.cc
@@ -227,9 +227,9 @@ const PegInfo pc_names[] =
     { CountType::SUM, "offload_fallback", "fast pattern offload search fallback attempts" },
     { CountType::SUM, "offload_failures", "fast pattern offload search failures" },
     { CountType::SUM, "offload_suspends", "fast pattern search suspends due to offload context chains" },
-    { CountType::SUM, "pcre_match_limit", "total number of times pcre hit the match limit" },
-    { CountType::SUM, "pcre_recursion_limit", "total number of times pcre hit the recursion limit" },
-    { CountType::SUM, "pcre_error", "total number of times pcre returns error" },
+    { CountType::SUM, "pcre2_match_limit", "total number of times pcre2 hit the match limit" },
+    { CountType::SUM, "pcre2_recursion_limit", "total number of times pcre2 hit the recursion limit" },
+    { CountType::SUM, "pcre2_error", "total number of times pcre2 returns error" },
     { CountType::SUM, "cont_creations", "total number of continuations created" },
     { CountType::SUM, "cont_recalls", "total number of continuations recalled" },
     { CountType::SUM, "cont_flows", "total number of flows using continuation" },
--- a/src/utils/stats.h
+++ b/src/utils/stats.h
@@ -60,9 +60,9 @@ struct PacketCount
     PegCount offload_fallback;
     PegCount offload_failures;
     PegCount offload_suspends;
-    PegCount pcre_match_limit;
-    PegCount pcre_recursion_limit;
-    PegCount pcre_error;
+    PegCount pcre2_match_limit;
+    PegCount pcre2_recursion_limit;
+    PegCount pcre2_error;
     PegCount cont_creations;
     PegCount cont_recalls;
     PegCount cont_flows;
--- a/src/utils/util.cc
+++ b/src/utils/util.cc
@@ -30,7 +30,8 @@
 #include <netdb.h>
 #include <openssl/crypto.h>
 #include <pcap.h>
-#include <pcre.h>
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>
 #include <pwd.h>
 #include <sys/file.h>
 #include <sys/resource.h>
@@ -105,10 +106,13 @@ void StoreSnortInfoStrings()
 
 int DisplayBanner()
 {
+    PCRE2_UCHAR pcre2_version[32];
     const char* ljv = LUAJIT_VERSION;
     while ( *ljv && !isdigit(*ljv) )
         ++ljv;
 
+    pcre2_config(PCRE2_CONFIG_VERSION, pcre2_version);
+
     LogMessage("\n");
     LogMessage("   ,,_     -*> Snort++ <*-\n");
 #ifdef BUILD
@@ -125,7 +129,7 @@ int DisplayBanner()
     LogMessage("           Using LuaJIT version %s\n", ljv);
     LogMessage("           Using %s\n", OpenSSL_version(SSLEAY_VERSION));
     LogMessage("           Using %s\n", pcap_lib_version());
-    LogMessage("           Using PCRE version %s\n", pcre_version());
+    LogMessage("           Using PCRE version %s\n", pcre2_version);
     LogMessage("           Using ZLIB version %s\n", zlib_version);
 #ifdef HAVE_HYPERSCAN
     LogMessage("           Using Hyperscan version %s\n", hs_version());
--- a/tools/snort2lua/config_states/config_api.cc
+++ b/tools/snort2lua/config_states/config_api.cc
@@ -105,13 +105,13 @@ extern const ConvertMap* min_ttl_map;
 extern const ConvertMap* na_policy_mode_map;
 extern const ConvertMap* new_ttl_map;
 extern const ConvertMap* nolog_map;
-extern const ConvertMap* nopcre_map;
+extern const ConvertMap* nopcre2_map;
 extern const ConvertMap* no_promisc_map;
 extern const ConvertMap* obfuscate_map;
 extern const ConvertMap* order_map;
 extern const ConvertMap* paf_max_map;
-extern const ConvertMap* pcre_match_limit_map;
-extern const ConvertMap* pcre_match_limit_recursion_map;
+extern const ConvertMap* pcre2_match_limit_map;
+extern const ConvertMap* pcre2_match_limit_recursion_map;
 extern const ConvertMap* pkt_count_map;
 extern const ConvertMap* ppm_map;
 extern const ConvertMap* policy_id_map;
@@ -224,13 +224,13 @@ const std::vector<const ConvertMap*> con
     na_policy_mode_map,
     new_ttl_map,
     nolog_map,
-    nopcre_map,
+    nopcre2_map,
     no_promisc_map,
     obfuscate_map,
     order_map,
     paf_max_map,
-    pcre_match_limit_map,
-    pcre_match_limit_recursion_map,
+    pcre2_match_limit_map,
+    pcre2_match_limit_recursion_map,
     pkt_count_map,
     ppm_map,
     policy_id_map,
--- a/tools/snort2lua/config_states/config_no_option.cc
+++ b/tools/snort2lua/config_states/config_no_option.cc
@@ -250,18 +250,18 @@ static const ConvertMap enable_mpls_over
 const ConvertMap* enable_mpls_overlapping_ip_map = &enable_mpls_overlapping_ip_api;
 
 /*************************************************
- ********************  nopcre  *******************
+ ********************  nopcre2  *******************
  *************************************************/
 
-static const std::string nopcre = "nopcre";
-static const std::string pcre_enable = "pcre_enable";
-static const ConvertMap nopcre_api =
+static const std::string nopcre2 = "nopcre2";
+static const std::string pcre2_enable = "pcre2_enable";
+static const ConvertMap nopcre2_api =
 {
-    nopcre,
-    config_false_no_opt_ctor<& nopcre, & detection, & pcre_enable>
+    nopcre2,
+    config_false_no_opt_ctor<& nopcre2, & detection, & pcre2_enable>
 };
 
-const ConvertMap* nopcre_map = &nopcre_api;
+const ConvertMap* nopcre2_map = &nopcre2_api;
 
 /*************************************************
  ******************  obfuscate  ******************
--- a/tools/snort2lua/config_states/config_one_int_option.cc
+++ b/tools/snort2lua/config_states/config_one_int_option.cc
@@ -217,30 +217,30 @@ static const ConvertMap new_ttl_api =
 const ConvertMap* new_ttl_map = &new_ttl_api;
 
 /*************************************************
- **************  pcre_match_limit   **************
+ **************  pcre2_match_limit   **************
  *************************************************/
 
-static const std::string pcre_match_limit = "pcre_match_limit";
-static const ConvertMap pcre_match_limit_api =
+static const std::string pcre2_match_limit = "pcre2_match_limit";
+static const ConvertMap pcre2_match_limit_api =
 {
-    pcre_match_limit,
-    config_int_ctor<& pcre_match_limit, & detection>,
+    pcre2_match_limit,
+    config_int_ctor<& pcre2_match_limit, & detection>,
 };
 
-const ConvertMap* pcre_match_limit_map = &pcre_match_limit_api;
+const ConvertMap* pcre2_match_limit_map = &pcre2_match_limit_api;
 
 /**************************************************
- **********  pcre_match_limit_recursion  **********
+ **********  pcre2_match_limit_recursion  **********
  **************************************************/
 
-static const std::string pcre_match_limit_recursion = "pcre_match_limit_recursion";
-static const ConvertMap pcre_match_limit_recursion_api =
+static const std::string pcre2_match_limit_recursion = "pcre_match_limit_recursion";
+static const ConvertMap pcre2_match_limit_recursion_api =
 {
-    pcre_match_limit_recursion,
-    config_int_ctor<& pcre_match_limit_recursion, & detection>,
+    pcre2_match_limit_recursion,
+    config_int_ctor<& pcre2_match_limit_recursion, & detection>,
 };
 
-const ConvertMap* pcre_match_limit_recursion_map = &pcre_match_limit_recursion_api;
+const ConvertMap* pcre2_match_limit_recursion_map = &pcre2_match_limit_recursion_api;
 
 /*************************************************
  ******************  pkt_count   *****************
--- a/tools/snort2lua/rule_states/CMakeLists.txt
+++ b/tools/snort2lua/rule_states/CMakeLists.txt
@@ -12,7 +12,7 @@ add_library( rule_states OBJECT
     rule_http_encode.cc
     rule_isdataat.cc
     rule_metadata.cc
-    rule_pcre.cc
+    rule_pcre2.cc
     rule_react.cc
     rule_reference.cc
     rule_replace.cc
--- a/tools/snort2lua/rule_states/rule_api.cc
+++ b/tools/snort2lua/rule_states/rule_api.cc
@@ -75,7 +75,7 @@ extern const ConvertMap* modbus_data_map
 extern const ConvertMap* modbus_func_map;
 extern const ConvertMap* modbus_unit_map;
 extern const ConvertMap* msg_map;
-extern const ConvertMap* pcre_map;
+extern const ConvertMap* pcre2_map;
 extern const ConvertMap* pkt_data_map;
 extern const ConvertMap* priority_map;
 extern const ConvertMap* protected_content_map;
@@ -159,7 +159,7 @@ const std::vector<const ConvertMap*> rul
     modbus_func_map,
     modbus_unit_map,
     msg_map,
-    pcre_map,
+    pcre2_map,
     pkt_data_map,
     priority_map,
     protected_content_map,
--- a/tools/snort2lua/rule_states/rule_pcre.cc
+++ /dev/null
@@ -1,159 +0,0 @@
-//--------------------------------------------------------------------------
-// Copyright (C) 2014-2024 Cisco and/or its affiliates. All rights reserved.
-//
-// This program is free software; you can redistribute it and/or modify it
-// under the terms of the GNU General Public License Version 2 as published
-// by the Free Software Foundation.  You may not use, modify or distribute
-// this program under any other version of the GNU General Public License.
-//
-// This program is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License along
-// with this program; if not, write to the Free Software Foundation, Inc.,
-// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-//--------------------------------------------------------------------------
-// rule_pcre.cc author Josh Rosenbaum <jrosenba@cisco.com>
-
-#include <sstream>
-#include <vector>
-
-#include "conversion_state.h"
-#include "helpers/converter.h"
-#include "helpers/s2l_util.h"
-#include "rule_api.h"
-
-namespace rules
-{
-namespace
-{
-class Pcre : public ConversionState
-{
-public:
-    Pcre(Converter& c) : ConversionState(c) { }
-    bool convert(std::istringstream& data) override;
-};
-} // namespace
-
-bool Pcre::convert(std::istringstream& data_stream)
-{
-    bool sticky_buffer_set = false;
-    std::string buffer = "pkt_data";
-
-    char delim = '/';
-    std::string pcre_str = util::get_rule_option_args(data_stream);
-    std::string pattern;
-    std::string new_opts;
-    std::string options;
-
-    if (pcre_str.front() == '!')
-    {
-        pattern += "!";
-        pcre_str.erase(pcre_str.begin());
-    }
-
-    if (pcre_str.front() != '"' || pcre_str.back() != '"')
-    {
-        rule_api.bad_rule(data_stream, "pattern must be enclosed in \"");
-        return set_next_rule_state(data_stream);
-    }
-
-    pcre_str.erase(pcre_str.begin());
-    pattern += '"';
-
-    if (pcre_str.front() == 'm')
-    {
-        pcre_str.erase(pcre_str.begin());
-        pattern += 'm';
-        delim = pcre_str.front();
-    }
-
-    const std::size_t pattern_end = pcre_str.rfind(delim);
-    if ((pcre_str.front() != delim) || (pattern_end == 0))
-    {
-        std::string tmp = "Regex must be enclosed in delim '";
-        tmp.append(delim, 1);
-        rule_api.bad_rule(data_stream, tmp + "'");
-        return set_next_rule_state(data_stream);
-    }
-
-    pattern += pcre_str.substr(0, pattern_end + 1);
-    options = pcre_str.substr(pattern_end + 1, std::string::npos);
-    new_opts = "";
-
-    for (char c : options )
-    {
-        std::string sticky_buffer = std::string(); // empty string
-
-        switch (c)
-        {
-        case 'B': sticky_buffer = "raw_data"; break;
-        case 'U': sticky_buffer = "http_uri"; break;
-        case 'P': sticky_buffer = "pcre_P_option_body"; break;
-        case 'H': sticky_buffer = "pcre_H_option_header"; break;
-        case 'M': sticky_buffer = "http_method"; break;
-        case 'C': sticky_buffer = "http_cookie"; break;
-        case 'I': sticky_buffer = "http_raw_uri"; break;
-        case 'D': sticky_buffer = "http_raw_header"; break;
-        case 'K': sticky_buffer = "http_raw_cookie"; break;
-        case 'S': sticky_buffer = "http_stat_code"; break;
-        case 'Y': sticky_buffer = "http_stat_msg"; break;
-        case 'i':
-        case 's':
-        case 'm':
-        case 'x':
-        case 'A':
-        case 'E':
-        case 'G':
-        case 'O':
-        case 'R':
-        case '"':     // end of reg_ex
-            new_opts += c;
-            break;
-        default:
-        {
-            std::string dlt_opt = "unknown option - '";
-            dlt_opt.append(1, c);
-            dlt_opt += "'";
-            rule_api.bad_rule(data_stream, dlt_opt);
-            break;
-        }
-        }
-
-        if (!sticky_buffer.empty())
-        {
-            buffer = sticky_buffer;
-
-            if (sticky_buffer_set)
-                rule_api.bad_rule(data_stream,
-                    "Two sticky buffers set for this regular expression!");
-            else
-                sticky_buffer_set = true;
-        }
-    }
-
-    rule_api.add_option("pcre", pattern + new_opts);
-
-    rule_api.set_curr_options_buffer(buffer);
-
-    return set_next_rule_state(data_stream);
-}
-
-/**************************
- *******  A P I ***********
- **************************/
-
-static ConversionState* ctor(Converter& c)
-{ return new Pcre(c); }
-
-static const ConvertMap pcre_api =
-{
-    "pcre",
-    ctor,
-};
-
-const ConvertMap* pcre_map = &pcre_api;
-} // namespace rules
-
--- /dev/null
+++ b/tools/snort2lua/rule_states/rule_pcre2.cc
@@ -0,0 +1,159 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2014-2024 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// rule_pcre2.cc author Josh Rosenbaum <jrosenba@cisco.com>
+
+#include <sstream>
+#include <vector>
+
+#include "conversion_state.h"
+#include "helpers/converter.h"
+#include "helpers/s2l_util.h"
+#include "rule_api.h"
+
+namespace rules
+{
+namespace
+{
+class Pcre2 : public ConversionState
+{
+public:
+    Pcre2(Converter& c) : ConversionState(c) { }
+    bool convert(std::istringstream& data) override;
+};
+} // namespace
+
+bool Pcre2::convert(std::istringstream& data_stream)
+{
+    bool sticky_buffer_set = false;
+    std::string buffer = "pkt_data";
+
+    char delim = '/';
+    std::string pcre2_str = util::get_rule_option_args(data_stream);
+    std::string pattern;
+    std::string new_opts;
+    std::string options;
+
+    if (pcre2_str.front() == '!')
+    {
+        pattern += "!";
+        pcre2_str.erase(pcre2_str.begin());
+    }
+
+    if (pcre2_str.front() != '"' || pcre2_str.back() != '"')
+    {
+        rule_api.bad_rule(data_stream, "pattern must be enclosed in \"");
+        return set_next_rule_state(data_stream);
+    }
+
+    pcre2_str.erase(pcre2_str.begin());
+    pattern += '"';
+
+    if (pcre2_str.front() == 'm')
+    {
+        pcre2_str.erase(pcre2_str.begin());
+        pattern += 'm';
+        delim = pcre2_str.front();
+    }
+
+    const std::size_t pattern_end = pcre2_str.rfind(delim);
+    if ((pcre2_str.front() != delim) || (pattern_end == 0))
+    {
+        std::string tmp = "Regex must be enclosed in delim '";
+        tmp.append(delim, 1);
+        rule_api.bad_rule(data_stream, tmp + "'");
+        return set_next_rule_state(data_stream);
+    }
+
+    pattern += pcre2_str.substr(0, pattern_end + 1);
+    options = pcre2_str.substr(pattern_end + 1, std::string::npos);
+    new_opts = "";
+
+    for (char c : options )
+    {
+        std::string sticky_buffer = std::string(); // empty string
+
+        switch (c)
+        {
+        case 'B': sticky_buffer = "raw_data"; break;
+        case 'U': sticky_buffer = "http_uri"; break;
+        case 'P': sticky_buffer = "pcre_P_option_body"; break;
+        case 'H': sticky_buffer = "pcre_H_option_header"; break;
+        case 'M': sticky_buffer = "http_method"; break;
+        case 'C': sticky_buffer = "http_cookie"; break;
+        case 'I': sticky_buffer = "http_raw_uri"; break;
+        case 'D': sticky_buffer = "http_raw_header"; break;
+        case 'K': sticky_buffer = "http_raw_cookie"; break;
+        case 'S': sticky_buffer = "http_stat_code"; break;
+        case 'Y': sticky_buffer = "http_stat_msg"; break;
+        case 'i':
+        case 's':
+        case 'm':
+        case 'x':
+        case 'A':
+        case 'E':
+        case 'G':
+        case 'O':
+        case 'R':
+        case '"':     // end of reg_ex
+            new_opts += c;
+            break;
+        default:
+        {
+            std::string dlt_opt = "unknown option - '";
+            dlt_opt.append(1, c);
+            dlt_opt += "'";
+            rule_api.bad_rule(data_stream, dlt_opt);
+            break;
+        }
+        }
+
+        if (!sticky_buffer.empty())
+        {
+            buffer = sticky_buffer;
+
+            if (sticky_buffer_set)
+                rule_api.bad_rule(data_stream,
+                    "Two sticky buffers set for this regular expression!");
+            else
+                sticky_buffer_set = true;
+        }
+    }
+
+    rule_api.add_option("pcre", pattern + new_opts);
+
+    rule_api.set_curr_options_buffer(buffer);
+
+    return set_next_rule_state(data_stream);
+}
+
+/**************************
+ *******  A P I ***********
+ **************************/
+
+static ConversionState* ctor(Converter& c)
+{ return new Pcre2(c); }
+
+static const ConvertMap pcre2_api =
+{
+    "pcre2",
+    ctor,
+};
+
+const ConvertMap* pcre2_map = &pcre2_api;
+} // namespace rules
+
--- a/tools/snort2lua/rule_states/rule_sd_pattern.cc
+++ b/tools/snort2lua/rule_states/rule_sd_pattern.cc
@@ -41,7 +41,7 @@ private:
 
 std::string SDPattern::convert_pattern(const std::string& pattern)
 {
-    const std::string unused_pcre_tokens("()[].+*^$|");
+    const std::string unused_pcre2_tokens("()[].+*^$|");
 
     std::string s3_pattern;
 
@@ -100,7 +100,7 @@ std::string SDPattern::convert_pattern(c
             break;
 
         default:
-            if (unused_pcre_tokens.find(sym) != std::string::npos)
+            if (unused_pcre2_tokens.find(sym) != std::string::npos)
                 s3_pattern.push_back('\\');
             s3_pattern.push_back(sym);
             break;
