diff --git a/.firefox.metadata b/.firefox.metadata
index 1e36ca2..208fc51 100644
--- a/.firefox.metadata
+++ b/.firefox.metadata
@@ -1,6 +1,6 @@
 b963b16f6879c5dbe6e33a3a3da058b494453922 SOURCES/cbindgen-vendor.tar.xz
-9319b0b809919b3a2848167200ebd9844fff4b07 SOURCES/firefox-115.8.0esr.processed-source.tar.xz
-ebdc56046476e31cca6a0e864565527eb156b715 SOURCES/firefox-langpacks-115.8.0esr-20240213.tar.xz
+b28310d67624787206243d4c9eff8496ecb4c9df SOURCES/firefox-115.9.1esr.processed-source.tar.xz
+a69c4cf421b2b0e84142b7a5e47ae382ba9e975b SOURCES/firefox-langpacks-115.9.1esr-20240322.tar.xz
 2dbf669fa4742e7065cc54cec19f96423032658b SOURCES/firefox-symbolic.svg
 2d8a6b2b30d5496735f49ffe8c8a7ede3a78a5ca SOURCES/mochitest-python.tar.gz
 d744f92e874688cc4b5376477dfdd639a97a6cd4 SOURCES/nspr-4.35.0-1.el8_1.src.rpm
diff --git a/.gitignore b/.gitignore
index c82c46a..0079962 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
 SOURCES/cbindgen-vendor.tar.xz
-SOURCES/firefox-115.8.0esr.processed-source.tar.xz
-SOURCES/firefox-langpacks-115.8.0esr-20240213.tar.xz
+SOURCES/firefox-115.9.1esr.processed-source.tar.xz
+SOURCES/firefox-langpacks-115.9.1esr-20240322.tar.xz
 SOURCES/firefox-symbolic.svg
 SOURCES/mochitest-python.tar.gz
 SOURCES/nspr-4.35.0-1.el8_1.src.rpm
diff --git a/README.debrand b/README.debrand
deleted file mode 100644
index 01c46d2..0000000
--- a/README.debrand
+++ /dev/null
@@ -1,2 +0,0 @@
-Warning: This package was configured for automatic debranding, but the changes
-failed to apply.
diff --git a/SOURCES/expat-CVE-2023-52425.patch b/SOURCES/expat-CVE-2023-52425.patch
new file mode 100644
index 0000000..bdd06ca
--- /dev/null
+++ b/SOURCES/expat-CVE-2023-52425.patch
@@ -0,0 +1,375 @@
+# erAck: backport of expat CVE-2023-52425 DoS fix
+# https://github.com/libexpat/libexpat/commit/34b598c5f594b015c513c73f06e7ced3323edbf1
+#
+--- firefox-115.9.0/parser/expat/lib/expat.h.expat-CVE-2023-52425	2024-03-11 20:36:11.000000000 +0100
++++ firefox-115.9.0/parser/expat/lib/expat.h	2024-03-13 20:46:45.648505015 +0100
+@@ -1045,6 +1045,10 @@ XMLPARSEAPI(const XML_Feature *)
+ XML_GetFeatureList(void);
+ 
+ 
++/* Added in Expat 2.6.0. */
++XMLPARSEAPI(XML_Bool)
++XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled);
++
+ /* Expat follows the semantic versioning convention.
+    See http://semver.org.
+ */
+--- firefox-115.9.0/parser/expat/lib/internal.h.expat-CVE-2023-52425	2024-03-11 20:36:11.000000000 +0100
++++ firefox-115.9.0/parser/expat/lib/internal.h	2024-03-14 00:14:39.334319725 +0100
+@@ -80,6 +80,7 @@
+ # endif
+ #endif
+ 
++#include "expat.h" // so we can use type XML_Parser below
+ 
+ #ifdef __cplusplus
+ extern "C" {
+@@ -90,6 +91,9 @@ void
+ align_limit_to_full_utf8_characters(const char * from, const char ** fromLimRef);
+ 
+ 
++extern XML_Bool g_reparseDeferralEnabledDefault; // written ONLY in runtests.c
++extern unsigned int g_parseAttempts;             // used for testing only
++
+ #ifdef __cplusplus
+ }
+ #endif
+--- firefox-115.9.0/parser/expat/lib/xmlparse.c.expat-CVE-2023-52425	2024-03-11 20:36:11.000000000 +0100
++++ firefox-115.9.0/parser/expat/lib/xmlparse.c	2024-03-13 22:55:14.844756009 +0100
+@@ -6,6 +6,7 @@
+ 
+ #define _GNU_SOURCE                     /* syscall prototype */
+ 
++#include <stdbool.h>
+ #include <stddef.h>
+ #include <string.h>                     /* memset(), memcpy() */
+ #include <assert.h>
+@@ -89,6 +90,9 @@ typedef char ICHAR;
+ /* Round up n to be a multiple of sz, where sz is a power of 2. */
+ #define ROUND_UP(n, sz) (((n) + ((sz) - 1)) & ~((sz) - 1))
+ 
++/* Do safe (NULL-aware) pointer arithmetic */
++#define EXPAT_SAFE_PTR_DIFF(p, q) (((p) && (q)) ? ((p) - (q)) : 0)
++
+ /* Handle the case where memmove() doesn't exist. */
+ #ifndef HAVE_MEMMOVE
+ #ifdef HAVE_BCOPY
+@@ -98,6 +102,8 @@ typedef char ICHAR;
+ #endif /* HAVE_BCOPY */
+ #endif /* HAVE_MEMMOVE */
+ 
++#define EXPAT_MIN(a, b) (((a) < (b)) ? (a) : (b))
++
+ #include "internal.h"
+ #include "xmltok.h"
+ #include "xmlrole.h"
+@@ -476,6 +482,9 @@ parserInit(XML_Parser parser, const XML_
+    ? 0 \
+    : ((*((pool)->ptr)++ = c), 1))
+ 
++XML_Bool g_reparseDeferralEnabledDefault = XML_TRUE; // write ONLY in runtests.c
++unsigned int g_parseAttempts = 0;                    // used for testing only
++
+ struct XML_ParserStruct {
+   /* The first member must be userData so that the XML_GetUserData
+      macro works. */
+@@ -491,6 +500,9 @@ struct XML_ParserStruct {
+   const char *m_bufferLim;
+   XML_Index m_parseEndByteIndex;
+   const char *m_parseEndPtr;
++  size_t m_partialTokenBytesBefore; /* used in heuristic to avoid O(n^2) */
++  XML_Bool m_reparseDeferralEnabled;
++  int m_lastBufferRequestSize;
+   XML_Char *m_dataBuf;
+   XML_Char *m_dataBufEnd;
+   XML_StartElementHandler m_startElementHandler;
+@@ -647,6 +659,9 @@ struct XML_ParserStruct {
+ #define bufferEnd (parser->m_bufferEnd)
+ #define parseEndByteIndex (parser->m_parseEndByteIndex)
+ #define parseEndPtr (parser->m_parseEndPtr)
++#define partialTokenBytesBefore (parser->m_partialTokenBytesBefore)
++#define reparseDeferralEnabled (parser->m_reparseDeferralEnabled)
++#define lastBufferRequestSize (parser->m_lastBufferRequestSize)
+ #define bufferLim (parser->m_bufferLim)
+ #define dataBuf (parser->m_dataBuf)
+ #define dataBufEnd (parser->m_dataBufEnd)
+@@ -887,6 +902,47 @@ get_hash_secret_salt(XML_Parser parser)
+   return parser->m_hash_secret_salt;
+ }
+ 
++static enum XML_Error
++callProcessor(XML_Parser parser, const char *start, const char *end,
++              const char **endPtr) {
++  const size_t have_now = EXPAT_SAFE_PTR_DIFF(end, start);
++
++  if (parser->m_reparseDeferralEnabled
++      && ! parser->m_parsingStatus.finalBuffer) {
++    // Heuristic: don't try to parse a partial token again until the amount of
++    // available data has increased significantly.
++    const size_t had_before = parser->m_partialTokenBytesBefore;
++    // ...but *do* try anyway if we're close to causing a reallocation.
++    size_t available_buffer
++        = EXPAT_SAFE_PTR_DIFF(parser->m_bufferPtr, parser->m_buffer);
++#if XML_CONTEXT_BYTES > 0
++    available_buffer -= EXPAT_MIN(available_buffer, XML_CONTEXT_BYTES);
++#endif
++    available_buffer
++        += EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_bufferEnd);
++    // m_lastBufferRequestSize is never assigned a value < 0, so the cast is ok
++    const bool enough
++        = (have_now >= 2 * had_before)
++          || ((size_t)parser->m_lastBufferRequestSize > available_buffer);
++
++    if (! enough) {
++      *endPtr = start; // callers may expect this to be set
++      return XML_ERROR_NONE;
++    }
++  }
++  g_parseAttempts += 1;
++  const enum XML_Error ret = parser->m_processor(parser, start, end, endPtr);
++  if (ret == XML_ERROR_NONE) {
++    // if we consumed nothing, remember what we had on this parse attempt.
++    if (*endPtr == start) {
++      parser->m_partialTokenBytesBefore = have_now;
++    } else {
++      parser->m_partialTokenBytesBefore = 0;
++    }
++  }
++  return ret;
++}
++
+ static XML_Bool  /* only valid for root parser */
+ startParsing(XML_Parser parser)
+ {
+@@ -1075,6 +1131,9 @@ parserInit(XML_Parser parser, const XML_
+   bufferEnd = buffer;
+   parseEndByteIndex = 0;
+   parseEndPtr = NULL;
++  partialTokenBytesBefore = 0;
++  reparseDeferralEnabled = g_reparseDeferralEnabledDefault;
++  lastBufferRequestSize = 0;
+   declElementType = NULL;
+   declAttributeId = NULL;
+   declEntity = NULL;
+@@ -1232,6 +1291,7 @@ XML_ExternalEntityParserCreate(XML_Parse
+      to worry which hash secrets each table has.
+   */
+   unsigned long oldhash_secret_salt;
++  XML_Bool oldReparseDeferralEnabled;
+ 
+   /* Validate the oldParser parameter before we pull everything out of it */
+   if (oldParser == NULL)
+@@ -1276,6 +1336,7 @@ XML_ExternalEntityParserCreate(XML_Parse
+      to worry which hash secrets each table has.
+   */
+   oldhash_secret_salt = hash_secret_salt;
++  oldReparseDeferralEnabled = reparseDeferralEnabled;
+ 
+ #ifdef XML_DTD
+   if (!context)
+@@ -1330,6 +1391,7 @@ XML_ExternalEntityParserCreate(XML_Parse
+   defaultExpandInternalEntities = oldDefaultExpandInternalEntities;
+   ns_triplets = oldns_triplets;
+   hash_secret_salt = oldhash_secret_salt;
++  reparseDeferralEnabled = oldReparseDeferralEnabled;
+   parentParser = oldParser;
+ #ifdef XML_DTD
+   paramEntityParsing = oldParamEntityParsing;
+@@ -1850,39 +1912,8 @@ XML_Parse(XML_Parser parser, const char
+     ps_parsing = XML_PARSING;
+   }
+ 
+-  if (len == 0) {
+-    ps_finalBuffer = (XML_Bool)isFinal;
+-    if (!isFinal)
+-      return XML_STATUS_OK;
+-    positionPtr = bufferPtr;
+-    parseEndPtr = bufferEnd;
+-
+-    /* If data are left over from last buffer, and we now know that these
+-       data are the final chunk of input, then we have to check them again
+-       to detect errors based on that fact.
+-    */
+-    errorCode = processor(parser, bufferPtr, parseEndPtr, &bufferPtr);
+-
+-    if (errorCode == XML_ERROR_NONE) {
+-      switch (ps_parsing) {
+-      case XML_SUSPENDED:
+-        XmlUpdatePosition(encoding, positionPtr, bufferPtr, &position);
+-        positionPtr = bufferPtr;
+-        return XML_STATUS_SUSPENDED;
+-      case XML_INITIALIZED:
+-      case XML_PARSING:
+-        ps_parsing = XML_FINISHED;
+-        /* fall through */
+-      default:
+-        return XML_STATUS_OK;
+-      }
+-    }
+-    eventEndPtr = eventPtr;
+-    processor = errorProcessor;
+-    return XML_STATUS_ERROR;
+-  }
+ #ifndef XML_CONTEXT_BYTES
+-  else if (bufferPtr == bufferEnd) {
++  if (bufferPtr == bufferEnd) {
+     const char *end;
+     int nLeftOver;
+     enum XML_Status result;
+@@ -1899,11 +1930,14 @@ XML_Parse(XML_Parser parser, const char
+        processor = errorProcessor;
+        return XML_STATUS_ERROR;
+     }
++    // though this isn't a buffer request, we assume that `len` is the app's
++    // preferred buffer fill size, and therefore save it here.
++    lastBufferRequestSize = len;
+     parseEndByteIndex += len;
+     positionPtr = s;
+     ps_finalBuffer = (XML_Bool)isFinal;
+ 
+-    errorCode = processor(parser, s, parseEndPtr = s + len, &end);
++    errorCode = callProcessor(parser, s, parseEndPtr = s + len, &end);
+ 
+     if (errorCode != XML_ERROR_NONE) {
+       eventEndPtr = eventPtr;
+@@ -1930,6 +1964,8 @@ XML_Parse(XML_Parser parser, const char
+     XmlUpdatePosition(encoding, positionPtr, end, &position);
+     nLeftOver = s + len - end;
+     if (nLeftOver) {
++#if 0
++// erAck: replace with XML_GetBuffer() below.
+       if (buffer == NULL || nLeftOver > bufferLim - buffer) {
+         /* avoid _signed_ integer overflow */
+         char *temp = NULL;
+@@ -1939,6 +1975,28 @@ XML_Parse(XML_Parser parser, const char
+                 ? (char *)MALLOC(bytesToAllocate)
+                 : (char *)REALLOC(buffer, bytesToAllocate));
+         }
++#endif
++#if 1
++// erAck: the original patch context had a call to XML_GetBuffer() instead:
++      // Back up and restore the parsing status to avoid XML_ERROR_SUSPENDED
++      // (and XML_ERROR_FINISHED) from XML_GetBuffer.
++      const enum XML_Parsing originalStatus = ps_parsing;
++      ps_parsing = XML_PARSING;
++      void *const temp = XML_GetBuffer(parser, nLeftOver);
++      ps_parsing = originalStatus;
++#endif
++      // GetBuffer may have overwritten this, but we want to remember what the
++      // app requested, not how many bytes were left over after parsing.
++      lastBufferRequestSize = len;
++#if 1
++      if (temp == NULL) {
++        // NOTE: parser->m_errorCode has already been set by XML_GetBuffer().
++        eventPtr = eventEndPtr = NULL;
++        processor = errorProcessor;
++        return XML_STATUS_ERROR;
++      }
++#endif
++#if 0
+         if (temp == NULL) {
+           errorCode = XML_ERROR_NO_MEMORY;
+           eventPtr = eventEndPtr = NULL;
+@@ -1948,6 +2006,7 @@ XML_Parse(XML_Parser parser, const char
+         buffer = temp;
+         bufferLim = buffer + bytesToAllocate;
+       }
++#endif
+       memcpy(buffer, end, nLeftOver);
+     }
+     bufferPtr = buffer;
+@@ -1959,15 +2018,14 @@ XML_Parse(XML_Parser parser, const char
+     return result;
+   }
+ #endif  /* not defined XML_CONTEXT_BYTES */
+-  else {
+-    void *buff = XML_GetBuffer(parser, len);
+-    if (buff == NULL)
+-      return XML_STATUS_ERROR;
+-    else {
+-      memcpy(buff, s, len);
+-      return XML_ParseBuffer(parser, len, isFinal);
+-    }
++  void *buff = XML_GetBuffer(parser, len);
++  if (buff == NULL)
++    return XML_STATUS_ERROR;
++  if (len > 0) {
++    assert(s != NULL); // make sure s==NULL && len!=0 was rejected above
++    memcpy(buff, s, len);
+   }
++  return XML_ParseBuffer(parser, len, isFinal);
+ }
+ 
+ enum XML_Status XMLCALL
+@@ -2001,7 +2059,7 @@ XML_ParseBuffer(XML_Parser parser, int l
+   parseEndByteIndex += len;
+   ps_finalBuffer = (XML_Bool)isFinal;
+ 
+-  errorCode = processor(parser, start, parseEndPtr, &bufferPtr);
++  errorCode = callProcessor(parser, start, parseEndPtr, &bufferPtr);
+ 
+   if (errorCode != XML_ERROR_NONE) {
+     eventEndPtr = eventPtr;
+@@ -2047,7 +2105,11 @@ XML_GetBuffer(XML_Parser parser, int len
+   default: ;
+   }
+ 
+-  if (len > bufferLim - bufferEnd) {
++  // whether or not the request succeeds, `len` seems to be the app's preferred
++  // buffer fill size; remember it.
++  lastBufferRequestSize = len;
++  if (len > EXPAT_SAFE_PTR_DIFF(bufferLim, bufferEnd)
++      || buffer == NULL) {
+ #ifdef XML_CONTEXT_BYTES
+     int keep;
+ #endif  /* defined XML_CONTEXT_BYTES */
+@@ -2063,7 +2125,9 @@ XML_GetBuffer(XML_Parser parser, int len
+       keep = XML_CONTEXT_BYTES;
+     neededSize += keep;
+ #endif  /* defined XML_CONTEXT_BYTES */
+-    if (neededSize  <= bufferLim - buffer) {
++    if (buffer && bufferPtr
++        && neededSize
++               <= EXPAT_SAFE_PTR_DIFF(bufferLim, buffer)) {
+ #ifdef XML_CONTEXT_BYTES
+       if (keep < bufferPtr - buffer) {
+         int offset = (int)(bufferPtr - buffer) - keep;
+@@ -2072,8 +2136,11 @@ XML_GetBuffer(XML_Parser parser, int len
+         bufferPtr -= offset;
+       }
+ #else
+-      memmove(buffer, bufferPtr, bufferEnd - bufferPtr);
+-      bufferEnd = buffer + (bufferEnd - bufferPtr);
++      memmove(buffer, bufferPtr,
++              EXPAT_SAFE_PTR_DIFF(bufferEnd, bufferPtr));
++      bufferEnd
++          = buffer
++            + EXPAT_SAFE_PTR_DIFF(bufferEnd, bufferPtr);
+       bufferPtr = buffer;
+ #endif  /* not defined XML_CONTEXT_BYTES */
+     }
+@@ -2171,7 +2238,7 @@ XML_ResumeParser(XML_Parser parser)
+   }
+   ps_parsing = XML_PARSING;
+ 
+-  errorCode = processor(parser, bufferPtr, parseEndPtr, &bufferPtr);
++  errorCode = callProcessor(parser, bufferPtr, parseEndPtr, &bufferPtr);
+ 
+   if (errorCode != XML_ERROR_NONE) {
+     eventEndPtr = eventPtr;
+@@ -2481,6 +2548,15 @@ MOZ_XML_ProcessingEntityValue(XML_Parser
+ }
+ /* END MOZILLA CHANGE */
+ 
++XML_Bool XMLCALL
++XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled) {
++  if (parser != NULL && (enabled == XML_TRUE || enabled == XML_FALSE)) {
++    parser->m_reparseDeferralEnabled = enabled;
++    return XML_TRUE;
++  }
++  return XML_FALSE;
++}
++
+ /* Initially tag->rawName always points into the parse buffer;
+    for those TAG instances opened while the current parse buffer was
+    processed, and not yet closed, we need to store tag->rawName in a more
diff --git a/SPECS/firefox.spec b/SPECS/firefox.spec
index fa99245..e1ecc12 100644
--- a/SPECS/firefox.spec
+++ b/SPECS/firefox.spec
@@ -122,7 +122,7 @@ end}
 # If set to .b2 or .b3 ... the processed source file needs to be renamed before upload, e.g.
 # firefox-102.8.0esr.b2.processed-source.tar.xz
 # When unset use processed source file name as is.
-##global buildnum             .b2
+##global buildnum .b2
 
 %bcond_without langpacks
 
@@ -132,7 +132,7 @@ end}
 
 Summary:        Mozilla Firefox Web browser
 Name:           firefox
-Version:        115.8.0
+Version:        115.9.1
 Release:        1%{?dist}
 URL:            https://www.mozilla.org/firefox/
 License:        MPLv1.1 or GPLv2+ or LGPLv2+
@@ -153,7 +153,7 @@ ExcludeArch:    %{ix86}
   %endif
 %endif
 %if 0%{?rhel} == 7
-ExcludeArch:    s390 ppc
+ExcludeArch:    aarch64 s390 ppc
 %endif
 
 # We can't use the official tarball as it contains some test files that use
@@ -163,7 +163,7 @@ ExcludeArch:    s390 ppc
 # Link to original tarball: https://archive.mozilla.org/pub/firefox/releases/%%{version}%%{?pre_version}/source/firefox-%%{version}%%{?pre_version}.source.tar.xz
 Source0:        firefox-%{version}%{?pre_version}%{?buildnum}.processed-source.tar.xz
 %if %{with langpacks}
-Source1:        firefox-langpacks-%{version}%{?pre_version}-20240213.tar.xz
+Source1:        firefox-langpacks-%{version}%{?pre_version}-20240322.tar.xz
 %endif
 Source2:        cbindgen-vendor.tar.xz
 Source3:        process-official-tarball
@@ -237,6 +237,7 @@ Patch201:       firefox-tests-xpcshell-freeze.patch
 
 # ---- Security patches ----
 Patch301:       CVE-2023-44488-libvpx.patch
+Patch302:       expat-CVE-2023-52425.patch
 
 # BUILD REQURES/REQUIRES
 %if %{?system_nss} && !0%{?bundle_nss}
@@ -1047,6 +1048,7 @@ echo "--------------------------------------------"
 cd media/libvpx/libvpx
 %patch -P301 -p1 -b .CVE-2023-44488-libvpx
 cd -
+%patch -P302 -p1 -b .expat-CVE-2023-52425
 
 %{__rm} -f .mozconfig
 %{__cp} %{SOURCE10} .mozconfig
@@ -1740,6 +1742,16 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :
 #---------------------------------------------------------------------
 
 %changelog
+* Fri Mar 22 2024 Eike Rathke <erack@redhat.com> - 115.9.1-1
+- Update to 115.9.1
+
+* Fri Mar 15 2024 Eike Rathke <erack@redhat.com> - 115.9.0-2
+- Update to 115.9.0 build2
+
+* Tue Mar 12 2024 Eike Rathke <erack@redhat.com> - 115.9.0-1
+- Update to 115.9.0 build1
+- Fix expat CVE-2023-52425
+
 * Tue Feb 13 2024 Eike Rathke <erack@redhat.com> - 115.8.0-1
 - Update to 115.8.0 build1