diff --git a/SOURCES/0157-fix-policy-cache-rule_str-for-rich-rules.patch b/SOURCES/0157-fix-policy-cache-rule_str-for-rich-rules.patch
new file mode 100644
index 0000000..e92a69d
--- /dev/null
+++ b/SOURCES/0157-fix-policy-cache-rule_str-for-rich-rules.patch
@@ -0,0 +1,80 @@
+From 748065d9d6a90548ecf07a62bf4cc0ab77696994 Mon Sep 17 00:00:00 2001
+From: Eric Garver <eric@garver.life>
+Date: Wed, 26 Aug 2020 11:38:36 -0400
+Subject: [PATCH 157/158] fix(policy): cache rule_str for rich rules
+
+There are various areas that we use list comprehensions to convert
+Rich_Rule to rule_str. This isn't cheap. Let's just cache the rule_str
+and avoid the cost.
+
+Fixes: rhbz 1871298
+(cherry picked from commit 5402724221a3dddc9c139663d28ababed4057cc6)
+(cherry picked from commit cc44042543a92393334d712ba8c3f3828aac33fb)
+---
+ src/firewall/core/io/zone.py | 17 ++++++++---------
+ 1 file changed, 8 insertions(+), 9 deletions(-)
+
+diff --git a/src/firewall/core/io/zone.py b/src/firewall/core/io/zone.py
+index 05368e9c73eb..57a43ce1e0ef 100644
+--- a/src/firewall/core/io/zone.py
++++ b/src/firewall/core/io/zone.py
+@@ -120,6 +120,7 @@ class Zone(IO_Object):
+         self.sources = [ ]
+         self.fw_config = None # to be able to check services and a icmp_blocks
+         self.rules = [ ]
++        self.rules_str = [ ]
+         self.icmp_block_inversion = False
+         self.combined = False
+         self.applied = False
+@@ -141,6 +142,7 @@ class Zone(IO_Object):
+         del self.sources[:]
+         self.fw_config = None # to be able to check services and a icmp_blocks
+         del self.rules[:]
++        del self.rules_str[:]
+         self.icmp_block_inversion = False
+         self.combined = False
+         self.applied = False
+@@ -163,17 +165,13 @@ class Zone(IO_Object):
+         self.interfaces = [u2b_if_py2(i) for i in self.interfaces]
+         self.sources = [u2b_if_py2(s) for s in self.sources]
+         self.rules = [u2b_if_py2(s) for s in self.rules]
+-
+-    def __getattr__(self, name):
+-        if name == "rules_str":
+-            rules_str = [str(rule) for rule in self.rules]
+-            return rules_str
+-        else:
+-            return getattr(super(Zone, self), name)
++        self.rules_str = [u2b_if_py2(s) for s in self.rules_str]
+ 
+     def __setattr__(self, name, value):
+         if name == "rules_str":
+             self.rules = [rich.Rich_Rule(rule_str=s) for s in value]
++            # must convert back to string to get the canonical string.
++            super(Zone, self).__setattr__(name, [str(s) for s in self.rules])
+         else:
+             super(Zone, self).__setattr__(name, value)
+ 
+@@ -292,6 +290,7 @@ class Zone(IO_Object):
+                 self.source_ports.append(port)
+         for rule in zone.rules:
+             self.rules.append(rule)
++            self.rules_str.append(str(rule))
+         if zone.icmp_block_inversion:
+             self.icmp_block_inversion = True
+ 
+@@ -669,9 +668,9 @@ class zone_ContentHandler(IO_Object_ContentHandler):
+                 except Exception as e:
+                     log.warning("%s: %s", e, str(self._rule))
+                 else:
+-                    if str(self._rule) not in \
+-                       [ str(x) for x in self.item.rules ]:
++                    if str(self._rule) not in self.item.rules_str:
+                         self.item.rules.append(self._rule)
++                        self.item.rules_str.append(str(self._rule))
+                     else:
+                         log.warning("Rule '%s' already set, ignoring.",
+                                     str(self._rule))
+-- 
+2.27.0
+
diff --git a/SOURCES/0158-test-zone-rich-rule-parsing-bottleneck.patch b/SOURCES/0158-test-zone-rich-rule-parsing-bottleneck.patch
new file mode 100644
index 0000000..0913f78
--- /dev/null
+++ b/SOURCES/0158-test-zone-rich-rule-parsing-bottleneck.patch
@@ -0,0 +1,55 @@
+From 7d3b14e91962df42a48f40f8ca5787f914413793 Mon Sep 17 00:00:00 2001
+From: Eric Garver <eric@garver.life>
+Date: Wed, 26 Aug 2020 14:28:45 -0400
+Subject: [PATCH 158/158] test(zone): rich rule parsing bottleneck
+
+Coverage for rhbz 1871298.
+Verify we can parse a large amount of rich rules in a reasonable time.
+
+This test took 3m before the fix and now takes 18s after the fix.
+Considering it "failed" after 45s should give us plenty of headroom.
+
+(cherry picked from commit ece30971412eedb9032b0d87233ca21ef9154830)
+(cherry picked from commit 10960dba93f8dfe607010655cbaeaa7f535bfa96)
+---
+ src/tests/regression/regression.at  |  1 +
+ src/tests/regression/rhbz1871298.at | 18 ++++++++++++++++++
+ 2 files changed, 19 insertions(+)
+ create mode 100644 src/tests/regression/rhbz1871298.at
+
+diff --git a/src/tests/regression/regression.at b/src/tests/regression/regression.at
+index 42f299f16f1a..6e4dc21384d3 100644
+--- a/src/tests/regression/regression.at
++++ b/src/tests/regression/regression.at
+@@ -28,3 +28,4 @@ m4_include([regression/gh567.at])
+ m4_include([regression/rhbz1779835.at])
+ m4_include([regression/gh599.at])
+ m4_include([regression/rhbz1839781.at])
++m4_include([regression/rhbz1871298.at])
+diff --git a/src/tests/regression/rhbz1871298.at b/src/tests/regression/rhbz1871298.at
+new file mode 100644
+index 000000000000..0689399d85ec
+--- /dev/null
++++ b/src/tests/regression/rhbz1871298.at
+@@ -0,0 +1,18 @@
++FWD_START_TEST([rich rule parsing bottleneck])
++AT_KEYWORDS(rich offline rhbz1871298)
++
++AT_SKIP_IF([! NS_CMD([which timeout >/dev/null 2>&1])])
++
++NS_CHECK([mkdir -p ./zones])
++NS_CHECK([echo '<?xml version="1.0" encoding="utf-8"?>' > ./zones/foobar.xml])
++NS_CHECK([echo "<zone>" >> ./zones/foobar.xml])
++NS_CHECK([echo "<short>foobar</short>" >> ./zones/foobar.xml])
++NS_CHECK([sh -c 'for I in $(seq 10000); do echo "<rule family=\"ipv4\"><port protocol=\"tcp\" port=\"$I\" /><accept/></rule>" >> ./zones/foobar.xml; done'])
++NS_CHECK([echo "</zone>" >> ./zones/foobar.xml])
++
++if test "x${FIREWALLD_DEFAULT_CONFIG}" != x ; then
++    FIREWALL_OFFLINE_CMD_ARGS+=" --default-config ${FIREWALLD_DEFAULT_CONFIG}"
++fi
++NS_CHECK([timeout 45 firewall-offline-cmd --system-config ./ $FIREWALL_OFFLINE_CMD_ARGS --check-config], 0, [ignore])
++
++FWD_END_TEST
+-- 
+2.27.0
+
diff --git a/SPECS/firewalld.spec b/SPECS/firewalld.spec
index 453daa4..5817730 100644
--- a/SPECS/firewalld.spec
+++ b/SPECS/firewalld.spec
@@ -8,7 +8,7 @@
 Summary: A firewall daemon with D-Bus interface providing a dynamic firewall
 Name: firewalld
 Version: 0.6.3
-Release: 11%{?dist}
+Release: 12%{?dist}
 URL:     http://www.firewalld.org
 License: GPLv2+
 Source0: https://github.com/firewalld/firewalld/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz
@@ -167,6 +167,8 @@ Patch153: 0153-fix-test-regression-gh599-fix-if-not-using-debug-out.patch
 Patch154: 0154-fix-test-regression-gh599-use-expr-to-be-more-portab.patch
 Patch155: 0155-feat-service-add-RH-Satellite-6-Capsule.patch
 Patch156: 0156-test-service-coverage-for-RH-Satellite-6.patch
+Patch157: 0157-fix-policy-cache-rule_str-for-rich-rules.patch
+Patch158: 0158-test-zone-rich-rule-parsing-bottleneck.patch
 
 BuildArch: noarch
 BuildRequires: desktop-file-utils
@@ -468,6 +470,9 @@ fi
 %{_mandir}/man1/firewall-config*.1*
 
 %changelog
+* Tue Sep 08 2020 Eric Garver <egarver@redhat.com> - 0.6.3-12
+- fix(zone): cache rule_str for rich rules
+
 * Wed Jun 10 2020 Eric Garver <egarver@redhat.com> - 0.6.3-11
 - feat(service): add RH-Satellite-6-Capsule