Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected], [email protected], 
[email protected]
Control: affects -1 + src:suricata
User: [email protected]
Usertags: pu

Dear stable release managers,
I'd like to hand in security patches for suricata 7.0.10-1+deb13u2
in Debian trixie patching all the open CVEs for suricata from
January 2026. In accordance with the security team, the CVEs will
not warrant DSAs and should be included in the next point release
please.

[ Reason ]
Security fixes for:
- CVE-2026-22258
- CVE-2026-22259
- CVE-2026-22261
- CVE-2026-22262
- CVE-2026-22264

[ Impact ]
Exploiting these CVEs can lead to reduced availability or crashes.

Here is the upstream changelog of Suricata 7.0.14 [1]:

- Security #8198: dcerpc: unbounded fragment buffering leads to
  memory exhaustion (7.0.x backport)(CRITICAL - CVE-2026-22258)
- Security #8200: dnp3: unbounded transaction growth
  (7.0.x backport)(HIGH - CVE-2026-22259)
- Security #8209: eve/alert: http xff handling can lead to
  denial of service (7.0.x backport)(LOW - CVE 2026-22261)
- Security #8112: datasets: stack overflow
  (7.0.x backport)(HIGH - CVE-2026-22262)
- Security #8192: detect/alert: heap-use-after-free on alert queue
  expansion (7.0.x backport)(HIGH - CVE-2026-22264)

Though the impact may seem to look rather small, the vulnerabilities
can be exploited quite easily by simply sending the 'wrong' packets
over the network. Suricata is therefore highly affected due to its
nature as an IDS by processing untrusted input from the network.

[ Tests ]
- autopkgtest runs the unit-tests and these are OK for the patched
  package [2].
- Furthermore I used suricata-verify, an upstream tool for additional
  testing with pcaps, which also has the same number of successful
  tests between deb13u2 and deb13u3. See stable [3] vs. patched [4].

[ Risks ]
Because of successful unit tests, the risk should be OK. Not patching
the vulnerabilities can lead to crashing detection and that would
miss the point of an IDS/IPS.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
- Applied the upstream patches for 7.0.14 fixing the CVEs. Added
  all commits/patches having the same upstream ticket numbers.
- d/p/CVE-2026-22259_[2-4].patch: Patches have to be adjusted and
  refreshed to fit for Suricata 7.0.10.
- With the fix for CVE-2026-22259, DNP3 has reduced the default
  maximum number of outstanding transactions from 500 down to 32.
  This means a small change in stable Suricata behaviour to fix the
  vulnerability!
  Read the update instructions for Suricata 7.0.14 for more
  details [6].
- Furthermore added Debian patch headers and changelog.

[ Other info ]
See the source-debdiff and the patches attached.
See the final branch at [5] which might be a bit easier to review,
because each change is documented as a single commit.

Thanks for your work!

[1] 
https://github.com/OISF/suricata/blob/163bd652dfa92959e918a952429b939fa81f7b88/ChangeLog
[2] https://salsa.debian.org/ecite/pkg-suricata/-/jobs/9094192
[3] https://salsa.debian.org/ecite/pkg-suricata/-/jobs/9101745
[4] https://salsa.debian.org/ecite/pkg-suricata/-/jobs/9094196
[5] https://salsa.debian.org/ecite/pkg-suricata/-/tree/debian/trixie
[6] https://docs.suricata.io/en/suricata-7.0.14/upgrade.html#upgrading-to-7-0-14
diff -Nru suricata-7.0.10/debian/changelog suricata-7.0.10/debian/changelog
--- suricata-7.0.10/debian/changelog    2025-12-10 20:12:20.000000000 +0100
+++ suricata-7.0.10/debian/changelog    2026-02-22 13:28:52.000000000 +0100
@@ -1,3 +1,32 @@
+suricata (1:7.0.10-1+deb13u3) trixie; urgency=medium
+
+  * Fix CVE-2026-22258 in 7.0.10.
+    Cherry-Picked from:
+    * f82a388d0283725cb76782cf64e8341cab370830
+    * df389f8a43a06c718bb336ea082d6c80d6fefda0
+    * c9b80e5affe073ce9d95d0c935a8d67647c83bf7
+  * Fix CVE-2026-22262 in 7.0.10.
+    Cherry-Picked from:
+    * 32609e6896f9079c175665a94005417cec7637eb
+    * 27a2180bceaa3477419c78c54fce364398d011f1
+  * Fix CVE-2026-22264 in 7.0.10.
+    Cherry-Picked from 5789a3d3760dbf33d93fc56c27bd9529e5bdc8f2.
+  * Fix CVE-2026-22259 in 7.0.10.
+    Cherry-Picked from:
+    * 63225d5f8ef64cc65164c0bb1800730842d54942
+    * 635af8dc8be09667689be71d781912718ca1aa49
+    * fdd79bdb14488244604729f1d68ca4bc60000dbd
+    * a6d950315d9b6c1e35c10c24d9bb7128d422c21f
+    With this fix, DNP3 has reduced the default maximum number of
+    outstanding transactions from 500 down to 32.
+    Read the update instructions for Suricata 7.0.14 for more details.
+  * Fix CVE-2026-22261 in 7.0.10.
+    Cherry-Picked from:
+    * 44d0c81f537f230e9215c769453fb4d7214217a1
+    * 7e704a3f50690b5f5d5cc573147ef41449fe37ac
+
+ -- Andreas Dolp <[email protected]>  Sun, 22 Feb 2026 13:28:52 +0100
+
 suricata (1:7.0.10-1+deb13u2) trixie; urgency=medium
 
   * Fix CVE-2025-64344 in 7.0.10.
diff -Nru suricata-7.0.10/debian/patches/CVE-2026-22258_1.patch 
suricata-7.0.10/debian/patches/CVE-2026-22258_1.patch
--- suricata-7.0.10/debian/patches/CVE-2026-22258_1.patch       1970-01-01 
01:00:00.000000000 +0100
+++ suricata-7.0.10/debian/patches/CVE-2026-22258_1.patch       2026-02-22 
13:21:42.000000000 +0100
@@ -0,0 +1,286 @@
+From f82a388d0283725cb76782cf64e8341cab370830 Mon Sep 17 00:00:00 2001
+From: Shivani Bhardwaj <[email protected]>
+Date: Tue, 6 Jan 2026 16:44:52 +0530
+Subject: [PATCH 1/3] dcerpc: add upper limit on stub data
+
+DCERPC parsers had no upper bounds when it came to extending the stub
+data buffer. Traffic can be crafted to bypass some internal parser
+conditions to create an indefinite buffering in the stub_data array that
+can make Suricata crash.
+
+Add a default limit of 1MiB and make it configurable for the user.
+
+Security 8182
+
+Co-authored-by: Philippe Antoine <[email protected]>
+(cherry picked from commit e412215af990feeffbb66c7dd9f392813a20ae50)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/f82a388d0283725cb76782cf64e8341cab370830.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8182
+Subject: Upstream fix for CVE-2026-22258 part 1
+---
+ rust/src/dcerpc/dcerpc.rs     | 31 +++++++++++++++++++++++++++++--
+ rust/src/dcerpc/dcerpc_udp.rs | 18 +++++++++++++-----
+ rust/src/smb/dcerpc.rs        | 31 +++++++++++++++++++++++--------
+ rust/src/smb/smb.rs           | 23 +++++++++++++++++++++++
+ suricata.yaml.in              |  4 ++++
+ 5 files changed, 92 insertions(+), 15 deletions(-)
+
+diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs
+index 5469b736b..7297ff82e 100644
+--- a/rust/src/dcerpc/dcerpc.rs
++++ b/rust/src/dcerpc/dcerpc.rs
+@@ -25,7 +25,9 @@ use std;
+ use std::cmp;
+ use std::ffi::CString;
+ use std::collections::VecDeque;
+-use crate::conf::conf_get;
++use crate::conf::{conf_get, get_memval};
++
++pub static mut DCERPC_MAX_STUB_SIZE: u32 = 1048576;
+ 
+ // Constant DCERPC UDP Header length
+ pub const DCERPC_HDR_LEN: u16 = 16;
+@@ -163,6 +165,11 @@ pub fn get_req_type_for_resp(t: u8) -> u8 {
+         _ => DCERPC_TYPE_UNKNOWN,
+     }
+ }
++#[inline(always)]
++pub fn cfg_max_stub_size() -> u32 {
++    unsafe { DCERPC_MAX_STUB_SIZE }
++}
++
+ 
+ #[derive(Default, Debug)]
+ pub struct DCERPCTransaction {
+@@ -1096,7 +1103,12 @@ fn evaluate_stub_params(
+     }
+ 
+     let input_slice = &input[..stub_len as usize];
+-    stub_data_buffer.extend_from_slice(input_slice);
++    let max_size = cfg_max_stub_size() as usize;
++    if (stub_data_buffer.len() + input_slice.len()) < max_size {
++        stub_data_buffer.extend_from_slice(input_slice);
++    } else if stub_data_buffer.len() < max_size {
++        stub_data_buffer.extend_from_slice(&input_slice[..max_size - 
stub_data_buffer.len()]);
++    }
+ 
+     stub_len
+ }
+@@ -1396,6 +1408,21 @@ pub unsafe extern "C" fn rs_dcerpc_register_parser() {
+             }
+         }
+         SCLogDebug!("Rust DCERPC parser registered.");
++        let retval = conf_get("app-layer.protocols.dcerpc.max-stub-size");
++        if let Some(val) = retval {
++            match get_memval(val) {
++                Ok(retval) => {
++                    if retval > 0 {
++                        DCERPC_MAX_STUB_SIZE = retval as u32;
++                    } else {
++                        SCLogError!("Invalid max-stub-size value");
++                    }
++                }
++                Err(_) => {
++                    SCLogError!("Invalid max-stub-size value");
++                }
++            }
++        }
+     } else {
+         SCLogDebug!("Protocol detector and parser disabled for DCERPC.");
+     }
+diff --git a/rust/src/dcerpc/dcerpc_udp.rs b/rust/src/dcerpc/dcerpc_udp.rs
+index d70ca1b53..0a6213a87 100644
+--- a/rust/src/dcerpc/dcerpc_udp.rs
++++ b/rust/src/dcerpc/dcerpc_udp.rs
+@@ -1,4 +1,4 @@
+-/* Copyright (C) 2020 Open Information Security Foundation
++/* Copyright (C) 2020-2026 Open Information Security Foundation
+  *
+  * You can copy, redistribute or modify this Program under the terms of
+  * the GNU General Public License version 2 as published by the Free
+@@ -19,7 +19,7 @@ use crate::applayer::{self, *};
+ use crate::core::{self, Direction, DIR_BOTH};
+ use crate::dcerpc::dcerpc::{
+     DCERPCTransaction, DCERPC_MAX_TX, DCERPC_TYPE_REQUEST, 
DCERPC_TYPE_RESPONSE, PFCL1_FRAG, PFCL1_LASTFRAG,
+-    rs_dcerpc_get_alstate_progress, ALPROTO_DCERPC, PARSER_NAME,
++    rs_dcerpc_get_alstate_progress, ALPROTO_DCERPC, PARSER_NAME, 
cfg_max_stub_size,
+ };
+ use nom7::Err;
+ use std;
+@@ -169,18 +169,27 @@ impl DCERPCUDPState {
+             tx.tx_data.updated_ts = true;
+             let done = (hdr.flags1 & PFCL1_FRAG) == 0 || (hdr.flags1 & 
PFCL1_LASTFRAG) != 0;
+ 
++            let max_size = cfg_max_stub_size() as usize;
+             match hdr.pkt_type {
+                 DCERPC_TYPE_REQUEST => {
+-                    tx.stub_data_buffer_ts.extend_from_slice(input);
+                     tx.frag_cnt_ts += 1;
++                    if input.len() + tx.stub_data_buffer_ts.len() < max_size {
++                        tx.stub_data_buffer_ts.extend_from_slice(input);
++                    } else if tx.stub_data_buffer_ts.len() < max_size {
++                        
tx.stub_data_buffer_ts.extend_from_slice(&input[..max_size - 
tx.stub_data_buffer_ts.len()]);
++                    }
+                     if done {
+                         tx.req_done = true;
+                     }
+                     return true;
+                 }
+                 DCERPC_TYPE_RESPONSE => {
+-                    tx.stub_data_buffer_tc.extend_from_slice(input);
+                     tx.frag_cnt_tc += 1;
++                    if input.len() + tx.stub_data_buffer_tc.len() < max_size {
++                        tx.stub_data_buffer_tc.extend_from_slice(input);
++                    } else if tx.stub_data_buffer_tc.len() < max_size {
++                        
tx.stub_data_buffer_tc.extend_from_slice(&input[..max_size - 
tx.stub_data_buffer_tc.len()]);
++                    }
+                     if done {
+                         tx.resp_done = true;
+                     }
+@@ -397,7 +406,6 @@ pub unsafe extern "C" fn rs_dcerpc_udp_register_parser() {
+     }
+ }
+ 
+-
+ #[cfg(test)]
+ mod tests {
+     use crate::applayer::AppLayerResult;
+diff --git a/rust/src/smb/dcerpc.rs b/rust/src/smb/dcerpc.rs
+index 6c2a2f934..1e62241bb 100644
+--- a/rust/src/smb/dcerpc.rs
++++ b/rust/src/smb/dcerpc.rs
+@@ -18,7 +18,7 @@
+ // written by Victor Julien
+ 
+ use uuid;
+-use crate::smb::smb::*;
++use crate::smb::smb::{cfg_max_stub_size, *};
+ use crate::smb::smb2::*;
+ use crate::smb::dcerpc_records::*;
+ use crate::smb::events::*;
+@@ -205,10 +205,15 @@ pub fn smb_write_dcerpc_record(state: &mut SMBState,
+                                 SCLogDebug!("previous CMD {} found at tx {} 
=> {:?}",
+                                         dcer.packet_type, tx.id, tx);
+                                 if let 
Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data {
+-                                    SCLogDebug!("additional frag of size {}", 
recr.data.len());
+-                                    
tdn.stub_data_ts.extend_from_slice(recr.data);
+                                     tdn.frag_cnt_ts += 1;
+-                                    SCLogDebug!("stub_data now {}", 
tdn.stub_data_ts.len());
++                                    let max_size = cfg_max_stub_size() as 
usize;
++                                    if recr.data.len() + 
tdn.stub_data_ts.len() < max_size {
++                                        SCLogDebug!("additional frag of size 
{}", recr.data.len());
++                                        
tdn.stub_data_ts.extend_from_slice(recr.data);
++                                        SCLogDebug!("stub_data now {}", 
tdn.stub_data_ts.len());
++                                    } else if tdn.stub_data_ts.len() < 
max_size {
++                                        
tdn.stub_data_ts.extend_from_slice(&recr.data[..max_size - 
tdn.stub_data_ts.len()]);
++                                    }
+                                 }
+                                 if dcer.last_frag {
+                                     SCLogDebug!("last frag set, so request 
side of DCERPC closed");
+@@ -240,12 +245,17 @@ pub fn smb_write_dcerpc_record(state: &mut SMBState,
+                             SCLogDebug!("DCERPC: REQUEST {:?}", recr);
+                             if let Some(SMBTransactionTypeData::DCERPC(ref 
mut tdn)) = tx.type_data {
+                                 SCLogDebug!("first frag size {}", 
recr.data.len());
+-                                tdn.stub_data_ts.extend_from_slice(recr.data);
+                                 tdn.opnum = recr.opnum;
+                                 tdn.context_id = recr.context_id;
+                                 tdn.frag_cnt_ts += 1;
+-                                SCLogDebug!("DCERPC: REQUEST opnum {} stub 
data len {}",
+-                                        tdn.opnum, tdn.stub_data_ts.len());
++                                let max_size = cfg_max_stub_size() as usize;
++                                if tdn.stub_data_ts.len() + recr.data.len() < 
max_size {
++                                    
tdn.stub_data_ts.extend_from_slice(recr.data);
++                                    SCLogDebug!("DCERPC: REQUEST opnum {} 
stub data len {}",
++                                            tdn.opnum, 
tdn.stub_data_ts.len());
++                                } else if tdn.stub_data_ts.len() < max_size {
++                                    
tdn.stub_data_ts.extend_from_slice(&recr.data[..max_size - 
tdn.stub_data_ts.len()]);
++                                }
+                             }
+                             if dcer.last_frag {
+                                 tx.request_done = true;
+@@ -407,8 +417,13 @@ fn dcerpc_response_handle(tx: &mut SMBTransaction,
+                     if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) 
= tx.type_data {
+                         SCLogDebug!("CMD 11 found at tx {}", tx.id);
+                         tdn.set_result(DCERPC_TYPE_RESPONSE);
+-                        tdn.stub_data_tc.extend_from_slice(respr.data);
++                        let max_size = cfg_max_stub_size() as usize;
+                         tdn.frag_cnt_tc += 1;
++                        if tdn.stub_data_tc.len() + respr.data.len() < 
max_size {
++                            tdn.stub_data_tc.extend_from_slice(respr.data);
++                        } else if tdn.stub_data_tc.len() < max_size {
++                            
tdn.stub_data_tc.extend_from_slice(&respr.data[..max_size - 
tdn.stub_data_tc.len()]);
++                        }
+                     }
+                     tx.vercmd.set_ntstatus(ntstatus);
+                     tx.response_done = dcer.last_frag;
+diff --git a/rust/src/smb/smb.rs b/rust/src/smb/smb.rs
+index c22bc9fc9..0a0fc1082 100644
+--- a/rust/src/smb/smb.rs
++++ b/rust/src/smb/smb.rs
+@@ -81,6 +81,8 @@ pub static mut SMB_CFG_MAX_WRITE_SIZE: u32 = 16777216;
+ pub static mut SMB_CFG_MAX_WRITE_QUEUE_SIZE: u32 = 67108864;
+ pub static mut SMB_CFG_MAX_WRITE_QUEUE_CNT: u32 = 64;
+ 
++pub static mut SMB_DCERPC_MAX_STUB_SIZE: u32 = 1048576;
++
+ static mut ALPROTO_SMB: AppProto = ALPROTO_UNKNOWN;
+ 
+ static mut SMB_MAX_TX: usize = 1024;
+@@ -2438,6 +2440,21 @@ pub unsafe extern "C" fn rs_smb_register_parser() {
+                 SCLogError!("Invalid value for smb.max-tx");
+             }
+         }
++       let retval = conf_get("app-layer.protocols.smb.dcerpc.max-stub-size");
++       if let Some(val) = retval {
++           match get_memval(val) {
++               Ok(retval) => {
++                    if retval > 0 {
++                        SMB_DCERPC_MAX_STUB_SIZE = retval as u32;
++                    } else {
++                        SCLogError!("Invalid max-stub-size value");
++                    }
++               }
++               Err(_) => {
++                    SCLogError!("Invalid max-stub-size value");
++               }
++           }
++       }
+         SCLogConfig!("read: max record size: {}, max queued chunks {}, max 
queued size {}",
+                 SMB_CFG_MAX_READ_SIZE, SMB_CFG_MAX_READ_QUEUE_CNT, 
SMB_CFG_MAX_READ_QUEUE_SIZE);
+         SCLogConfig!("write: max record size: {}, max queued chunks {}, max 
queued size {}",
+@@ -2446,3 +2463,9 @@ pub unsafe extern "C" fn rs_smb_register_parser() {
+         SCLogDebug!("Protocol detector and parser disabled for SMB.");
+     }
+ }
++
++#[inline(always)]
++pub fn cfg_max_stub_size() -> u32 {
++    unsafe { SMB_DCERPC_MAX_STUB_SIZE }
++}
++
+diff --git a/suricata.yaml.in b/suricata.yaml.in
+index 7640f2b62..eab6ca500 100644
+--- a/suricata.yaml.in
++++ b/suricata.yaml.in
+@@ -951,6 +951,8 @@ app-layer:
+       enabled: yes
+       # Maximum number of live DCERPC transactions per flow
+       # max-tx: 1024
++      #max-stub-size: 1MiB
++
+     ftp:
+       enabled: yes
+       # memcap: 64mb
+@@ -1015,6 +1017,8 @@ app-layer:
+ 
+       # Stream reassembly size for SMB streams. By default track it 
completely.
+       #stream-depth: 0
++      #dcerpc:
++      #  max-stub-size: 1MiB
+ 
+     nfs:
+       enabled: yes
+-- 
+2.47.3
+
diff -Nru suricata-7.0.10/debian/patches/CVE-2026-22258_2.patch 
suricata-7.0.10/debian/patches/CVE-2026-22258_2.patch
--- suricata-7.0.10/debian/patches/CVE-2026-22258_2.patch       1970-01-01 
01:00:00.000000000 +0100
+++ suricata-7.0.10/debian/patches/CVE-2026-22258_2.patch       2026-02-22 
13:21:42.000000000 +0100
@@ -0,0 +1,54 @@
+From df389f8a43a06c718bb336ea082d6c80d6fefda0 Mon Sep 17 00:00:00 2001
+From: Shivani Bhardwaj <[email protected]>
+Date: Wed, 7 Jan 2026 10:33:57 +0530
+Subject: [PATCH 2/3] doc: add dcerpc.max-stub-size config param
+
+(cherry picked from commit 6702791a9c4463858c8b54ee8678fd4f5fbe831a)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/df389f8a43a06c718bb336ea082d6c80d6fefda0.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8182
+Subject: Upstream fix for CVE-2026-22258 part 2
+---
+ doc/userguide/configuration/suricata-yaml.rst | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+diff --git a/doc/userguide/configuration/suricata-yaml.rst 
b/doc/userguide/configuration/suricata-yaml.rst
+index bd414fbc6..8ea093b51 100644
+--- a/doc/userguide/configuration/suricata-yaml.rst
++++ b/doc/userguide/configuration/suricata-yaml.rst
+@@ -1674,6 +1674,9 @@ Several options are available for limiting record sizes 
and data chunk tracking.
+       max-write-queue-size: 16mb
+       max-write-queue-cnt: 16
+ 
++      dcerpc:
++        max-stub-size: 1MiB
++
+ The `max-read-size` option can be set to control the max size of accepted
+ READ records. Events will be raised if a READ request asks for too much data
+ and/or if READ responses are too big. A value of 0 disables the checks.
+@@ -1685,6 +1688,8 @@ data. A value of 0 disables the checks.
+ Additionally if the `max-read-size` or `max-write-size` values in the
+ "negotiate protocol response" exceeds this limit an event will also be raised.
+ 
++To control the size of the DCERPC stub data, `dcerpc.max-stub-size` should be
++used. It is by default set to 1MiB.
+ 
+ For file tracking, extraction and file data inspection the parser queues up
+ out of order data chunks for both READs and WRITEs. To avoid using too much
+@@ -1710,6 +1715,13 @@ the limits are exceeded, and an event will be raised.
+ `max-write-queue-size` and `max-write-queue-cnt` are as the READ variants,
+ but then for WRITEs.
+ 
++Configure DCERPC
++~~~~~~~~~~~~~~~~
++
++DCERPC has one parameter that can be customized.
++`max-stub-size` is used to control the stub data size of a DCERPC 
request/response. By
++default, it is set to 1MiB.
++
+ Configure HTTP2
+ ~~~~~~~~~~~~~~~
+ 
+-- 
+2.47.3
+
diff -Nru suricata-7.0.10/debian/patches/CVE-2026-22258_3.patch 
suricata-7.0.10/debian/patches/CVE-2026-22258_3.patch
--- suricata-7.0.10/debian/patches/CVE-2026-22258_3.patch       1970-01-01 
01:00:00.000000000 +0100
+++ suricata-7.0.10/debian/patches/CVE-2026-22258_3.patch       2026-02-22 
13:21:42.000000000 +0100
@@ -0,0 +1,73 @@
+From c9b80e5affe073ce9d95d0c935a8d67647c83bf7 Mon Sep 17 00:00:00 2001
+From: Philippe Antoine <[email protected]>
+Date: Thu, 8 Jan 2026 14:48:40 +0100
+Subject: [PATCH 3/3] dcerpc: use saturating_add to count fragments
+
+And do not overflow if we have traffic with more than 65K fragments
+
+(cherry picked from commit a48200b9e5befb1f0aa45ad5b33e2664e6a9fa41)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/c9b80e5affe073ce9d95d0c935a8d67647c83bf7.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8182
+Subject: Upstream fix for CVE-2026-22258 part 3
+---
+ rust/src/dcerpc/dcerpc_udp.rs | 4 ++--
+ rust/src/smb/dcerpc.rs        | 6 +++---
+ 2 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/rust/src/dcerpc/dcerpc_udp.rs b/rust/src/dcerpc/dcerpc_udp.rs
+index 0a6213a87..87fa9764b 100644
+--- a/rust/src/dcerpc/dcerpc_udp.rs
++++ b/rust/src/dcerpc/dcerpc_udp.rs
+@@ -172,7 +172,7 @@ impl DCERPCUDPState {
+             let max_size = cfg_max_stub_size() as usize;
+             match hdr.pkt_type {
+                 DCERPC_TYPE_REQUEST => {
+-                    tx.frag_cnt_ts += 1;
++                    tx.frag_cnt_ts = tx.frag_cnt_ts.saturating_add(1);
+                     if input.len() + tx.stub_data_buffer_ts.len() < max_size {
+                         tx.stub_data_buffer_ts.extend_from_slice(input);
+                     } else if tx.stub_data_buffer_ts.len() < max_size {
+@@ -184,7 +184,7 @@ impl DCERPCUDPState {
+                     return true;
+                 }
+                 DCERPC_TYPE_RESPONSE => {
+-                    tx.frag_cnt_tc += 1;
++                    tx.frag_cnt_tc = tx.frag_cnt_tc.saturating_add(1);
+                     if input.len() + tx.stub_data_buffer_tc.len() < max_size {
+                         tx.stub_data_buffer_tc.extend_from_slice(input);
+                     } else if tx.stub_data_buffer_tc.len() < max_size {
+diff --git a/rust/src/smb/dcerpc.rs b/rust/src/smb/dcerpc.rs
+index 1e62241bb..5cb1adeba 100644
+--- a/rust/src/smb/dcerpc.rs
++++ b/rust/src/smb/dcerpc.rs
+@@ -205,7 +205,7 @@ pub fn smb_write_dcerpc_record(state: &mut SMBState,
+                                 SCLogDebug!("previous CMD {} found at tx {} 
=> {:?}",
+                                         dcer.packet_type, tx.id, tx);
+                                 if let 
Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data {
+-                                    tdn.frag_cnt_ts += 1;
++                                    tdn.frag_cnt_ts = 
tdn.frag_cnt_ts.saturating_add(1);
+                                     let max_size = cfg_max_stub_size() as 
usize;
+                                     if recr.data.len() + 
tdn.stub_data_ts.len() < max_size {
+                                         SCLogDebug!("additional frag of size 
{}", recr.data.len());
+@@ -247,7 +247,7 @@ pub fn smb_write_dcerpc_record(state: &mut SMBState,
+                                 SCLogDebug!("first frag size {}", 
recr.data.len());
+                                 tdn.opnum = recr.opnum;
+                                 tdn.context_id = recr.context_id;
+-                                tdn.frag_cnt_ts += 1;
++                                tdn.frag_cnt_ts = 
tdn.frag_cnt_ts.saturating_add(1);
+                                 let max_size = cfg_max_stub_size() as usize;
+                                 if tdn.stub_data_ts.len() + recr.data.len() < 
max_size {
+                                     
tdn.stub_data_ts.extend_from_slice(recr.data);
+@@ -418,7 +418,7 @@ fn dcerpc_response_handle(tx: &mut SMBTransaction,
+                         SCLogDebug!("CMD 11 found at tx {}", tx.id);
+                         tdn.set_result(DCERPC_TYPE_RESPONSE);
+                         let max_size = cfg_max_stub_size() as usize;
+-                        tdn.frag_cnt_tc += 1;
++                        tdn.frag_cnt_tc = tdn.frag_cnt_tc.saturating_add(1);
+                         if tdn.stub_data_tc.len() + respr.data.len() < 
max_size {
+                             tdn.stub_data_tc.extend_from_slice(respr.data);
+                         } else if tdn.stub_data_tc.len() < max_size {
+-- 
+2.47.3
+
diff -Nru suricata-7.0.10/debian/patches/CVE-2026-22259_1.patch 
suricata-7.0.10/debian/patches/CVE-2026-22259_1.patch
--- suricata-7.0.10/debian/patches/CVE-2026-22259_1.patch       1970-01-01 
01:00:00.000000000 +0100
+++ suricata-7.0.10/debian/patches/CVE-2026-22259_1.patch       2026-02-22 
13:21:42.000000000 +0100
@@ -0,0 +1,38 @@
+From 63225d5f8ef64cc65164c0bb1800730842d54942 Mon Sep 17 00:00:00 2001
+From: Jason Ish <[email protected]>
+Date: Tue, 6 Jan 2026 16:15:09 -0600
+Subject: [PATCH 1/4] dnp3: check done state, not complete state for progress
+
+Complete is a flag used to tell if the message was completely parsed,
+as not all messages may be completely parsed if we don't know all
+their objects. However, they are still "done".
+
+In the alstate-progress callback, check the done flag, not the
+complete flag.
+
+Ticket: #8181
+(cherry picked from commit d61eef9a8a0d92921989479de15e5cbfec3251a9)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/63225d5f8ef64cc65164c0bb1800730842d54942.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8181
+Subject: Upstream fix for CVE-2026-22259 part 1
+---
+ src/app-layer-dnp3.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/app-layer-dnp3.c b/src/app-layer-dnp3.c
+index ecae4ae63..1a8b3cb63 100644
+--- a/src/app-layer-dnp3.c
++++ b/src/app-layer-dnp3.c
+@@ -1438,7 +1438,7 @@ static int DNP3GetAlstateProgress(void *tx, uint8_t 
direction)
+         SCReturnInt(1);
+     }
+ 
+-    if (dnp3tx->complete)
++    if (dnp3tx->done)
+         retval = 1;
+ 
+     SCReturnInt(retval);
+-- 
+2.47.3
+
diff -Nru suricata-7.0.10/debian/patches/CVE-2026-22259_2.patch 
suricata-7.0.10/debian/patches/CVE-2026-22259_2.patch
--- suricata-7.0.10/debian/patches/CVE-2026-22259_2.patch       1970-01-01 
01:00:00.000000000 +0100
+++ suricata-7.0.10/debian/patches/CVE-2026-22259_2.patch       2026-02-22 
13:21:42.000000000 +0100
@@ -0,0 +1,135 @@
+From 635af8dc8be09667689be71d781912718ca1aa49 Mon Sep 17 00:00:00 2001
+From: Jason Ish <[email protected]>
+Date: Tue, 6 Jan 2026 11:06:40 -0600
+Subject: [PATCH 2/4] dnp3: reduce flood threshold to 32 and make configurable
+
+Lower the number of unreplied requests from 500 to 32 to consider a
+flood. At the very least this is an anomaly given the DNP3 spec mentions
+that DNP3 should only have one outstanding request at a time, with an
+exception for unsolicited responses, so in practice no more than 2
+should be seen.
+
+Additionally make this value configurable by introducing the max-tx
+parameter.
+
+Ticket: #8181
+(cherry picked from commit a16f087b93be1ff2f2edf47371866ad9b28593c1)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/635af8dc8be09667689be71d781912718ca1aa49.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8181
+Subject: Upstream fix for CVE-2026-22259 part 2
+---
+ doc/userguide/upgrade.rst | 10 ++++++++++
+ src/app-layer-dnp3.c      | 29 +++++++++++++++++++----------
+ suricata.yaml.in          |  1 +
+ 3 files changed, 30 insertions(+), 10 deletions(-)
+
+--- a/doc/userguide/upgrade.rst
++++ b/doc/userguide/upgrade.rst
+@@ -34,6 +34,16 @@
+ this guide. Those features are either not enabled by default or require
+ dedicated new configuration.
+ 
++Upgrading to 7.0.14 (trixie-security 1:7.0.10-1~bpo13u3)
++-------------------
++
++Other Changes
++~~~~~~~~~~~~~
++- ``dnp3`` has reduced the default maximum number of outstanding
++  transactions from 500 down to 32. A ``max-tx`` parameter has been
++  added to the ``dnp3`` parser for users that need a larger number of
++  in-flight transactions.
++
+ Upgrading to 7.0.9
+ ------------------
+ - The AF_PACKET default block size for both TPACKET_V2 and TPACKET_V3
+--- a/src/app-layer-dnp3.c
++++ b/src/app-layer-dnp3.c
+@@ -40,9 +40,6 @@
+ #include "app-layer-dnp3.h"
+ #include "app-layer-dnp3-objects.h"
+ 
+-/* Default number of unreplied requests to be considered a flood. */
+-#define DNP3_DEFAULT_REQ_FLOOD_COUNT 500
+-
+ #define DNP3_DEFAULT_PORT "20000"
+ 
+ /* Expected values for the start bytes. */
+@@ -93,6 +90,14 @@
+ /* Extract the range code from the object qualifier. */
+ #define DNP3_OBJ_RANGE(x)  (x & 0xf)
+ 
++/* Default number of unreplied requests to be considered a flood.
++ *
++ * DNP3 is a request/response SCADA protocol with typically only 1-2
++ * transactions in flight. But set a limit high enough to allow for
++ * some pipelining but reduce the chance of memory exhaustion
++ * attacks. */
++static uint64_t dnp3_max_tx = 32;
++
+ /* Decoder event map. */
+ SCEnumCharMap dnp3_decoder_event_table[] = {
+     {"FLOODED",           DNP3_DECODER_EVENT_FLOODED},
+@@ -514,7 +519,7 @@
+     TAILQ_INSERT_TAIL(&dnp3->tx_list, tx, next);
+ 
+     /* Check for flood state. */
+-    if (dnp3->unreplied > DNP3_DEFAULT_REQ_FLOOD_COUNT) {
++    if (dnp3->unreplied > dnp3_max_tx && !dnp3->flooded) {
+         DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_FLOODED);
+         dnp3->flooded = 1;
+     }
+@@ -1384,7 +1389,7 @@
+         dnp3->unreplied--;
+ 
+         /* Check flood state. */
+-        if (dnp3->flooded && dnp3->unreplied < DNP3_DEFAULT_REQ_FLOOD_COUNT) {
++        if (dnp3->flooded && dnp3->unreplied < dnp3_max_tx) {
+             dnp3->flooded = 0;
+         }
+ 
+@@ -1430,8 +1435,7 @@
+     int retval = 0;
+ 
+     /* If flooded, "ack" old transactions. */
+-    if (dnp3->flooded && (dnp3->transaction_max -
+-            dnp3tx->tx_num >= DNP3_DEFAULT_REQ_FLOOD_COUNT)) {
++    if (dnp3->flooded && (dnp3->transaction_max - dnp3tx->tx_num >= 
dnp3_max_tx)) {
+         SCLogDebug("flooded: returning tx as done.");
+         SCReturnInt(1);
+     }
+@@ -1604,8 +1608,13 @@
+         AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_DNP3,
+             DNP3GetTxData);
+         AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_DNP3, 
DNP3GetStateData);
+-    }
+-    else {
++
++        /* Parse max-tx configuration. */
++        intmax_t value = 0;
++        if (ConfGetInt("app-layer.protocols.dnp3.max-tx", &value)) {
++            dnp3_max_tx = (uint64_t)value;
++        }
++    } else {
+         SCLogConfig("Parser disabled for protocol %s. "
+             "Protocol detection still on.", proto_name);
+     }
+@@ -2252,7 +2261,7 @@
+     FAIL_IF_NOT(tx->done);
+     FAIL_IF_NOT(DNP3GetAlstateProgress(tx, STREAM_TOSERVER));
+ 
+-    for (int i = 0; i < DNP3_DEFAULT_REQ_FLOOD_COUNT - 1; i++) {
++    for (uint64_t i = 0; i < dnp3_max_tx - 1; i++) {
+         SCMutexLock(&flow.m);
+         FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
+                 STREAM_TOSERVER, request, sizeof(request)));
+--- a/suricata.yaml.in
++++ b/suricata.yaml.in
+@@ -1161,6 +1161,7 @@
+       enabled: no
+       detection-ports:
+         dp: 20000
++      #max-tx: 32
+ 
+     # SCADA EtherNet/IP and CIP protocol support
+     enip:
diff -Nru suricata-7.0.10/debian/patches/CVE-2026-22259_3.patch 
suricata-7.0.10/debian/patches/CVE-2026-22259_3.patch
--- suricata-7.0.10/debian/patches/CVE-2026-22259_3.patch       1970-01-01 
01:00:00.000000000 +0100
+++ suricata-7.0.10/debian/patches/CVE-2026-22259_3.patch       2026-02-22 
13:21:42.000000000 +0100
@@ -0,0 +1,138 @@
+From fdd79bdb14488244604729f1d68ca4bc60000dbd Mon Sep 17 00:00:00 2001
+From: Jason Ish <[email protected]>
+Date: Wed, 7 Jan 2026 09:23:09 -0600
+Subject: [PATCH 3/4] dnp3: set a bound on the number of points per message
+
+16384 is used as the max, but a configuration parameter has been
+provided. The reason for setting an upper bound is that bit flags can
+create a memory amplification as we parse them into individual data
+structures.
+
+Ticket: #8181
+(cherry picked from commit 3a32bb5743c35afb3278a6448f7e9669512dbe92)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/fdd79bdb14488244604729f1d68ca4bc60000dbd.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8181
+Subject: Upstream fix for CVE-2026-22259 part 3
+---
+ doc/userguide/upgrade.rst |  9 +++++----
+ rules/dnp3-events.rules   |  5 +++++
+ src/app-layer-dnp3.c      | 33 ++++++++++++++++++++++++++-------
+ src/app-layer-dnp3.h      |  1 +
+ suricata.yaml.in          |  1 +
+ 5 files changed, 38 insertions(+), 11 deletions(-)
+
+--- a/doc/userguide/upgrade.rst
++++ b/doc/userguide/upgrade.rst
+@@ -39,10 +39,11 @@
+ 
+ Other Changes
+ ~~~~~~~~~~~~~
+-- ``dnp3`` has reduced the default maximum number of outstanding
+-  transactions from 500 down to 32. A ``max-tx`` parameter has been
+-  added to the ``dnp3`` parser for users that need a larger number of
+-  in-flight transactions.
++- ``dnp3`` has reduced the maximum number of open transactions from
++  500 down to 32, and the maximum number of points per message from
++  unbounded to 16384. Configuration options, ``max-tx`` and
++  ``max-points`` have been added for users who may need to change
++  these defaults.
+ 
+ Upgrading to 7.0.9
+ ------------------
+--- a/rules/dnp3-events.rules
++++ b/rules/dnp3-events.rules
+@@ -24,3 +24,8 @@
+ # Unknown object.
+ alert dnp3 any any -> any any (msg:"SURICATA DNP3 Unknown object"; \
+       app-layer-event:dnp3.unknown_object; classtype:protocol-command-decode; 
sid:2270004; rev:2;)
++
++# Too many points in a message.
++alert dnp3 any any -> any any (msg:"SURICATA DNP3 Too many points in 
message"; \
++      app-layer-event:dnp3.too_many_points; \
++      classtype:protocol-command-decode; sid:2270005; rev:1;)
+--- a/src/app-layer-dnp3.c
++++ b/src/app-layer-dnp3.c
+@@ -98,15 +98,19 @@
+  * attacks. */
+ static uint64_t dnp3_max_tx = 32;
+ 
++/* The maximum number of points allowed per message (configurable). */
++static uint64_t max_points = 16384;
++
+ /* Decoder event map. */
+ SCEnumCharMap dnp3_decoder_event_table[] = {
+-    {"FLOODED",           DNP3_DECODER_EVENT_FLOODED},
+-    {"LEN_TOO_SMALL",     DNP3_DECODER_EVENT_LEN_TOO_SMALL},
+-    {"BAD_LINK_CRC",      DNP3_DECODER_EVENT_BAD_LINK_CRC},
+-    {"BAD_TRANSPORT_CRC", DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC},
+-    {"MALFORMED",         DNP3_DECODER_EVENT_MALFORMED},
+-    {"UNKNOWN_OBJECT",    DNP3_DECODER_EVENT_UNKNOWN_OBJECT},
+-    {NULL, -1},
++    { "FLOODED", DNP3_DECODER_EVENT_FLOODED },
++    { "LEN_TOO_SMALL", DNP3_DECODER_EVENT_LEN_TOO_SMALL },
++    { "BAD_LINK_CRC", DNP3_DECODER_EVENT_BAD_LINK_CRC },
++    { "BAD_TRANSPORT_CRC", DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC },
++    { "MALFORMED", DNP3_DECODER_EVENT_MALFORMED },
++    { "UNKNOWN_OBJECT", DNP3_DECODER_EVENT_UNKNOWN_OBJECT },
++    { "TOO_MANY_POINTS", DNP3_DECODER_EVENT_TOO_MANY_POINTS },
++    { NULL, -1 },
+ };
+ 
+ /* Calculate the next transport sequence number. */
+@@ -709,6 +713,7 @@
+     uint32_t len, DNP3ObjectList *objects)
+ {
+     int retval = 0;
++    uint64_t point_count = 0;
+ 
+     if (buf == NULL || len == 0) {
+         return 1;
+@@ -839,6 +844,13 @@
+             goto next;
+         }
+ 
++        /* Check if we've exceeded the maximum number of points per message. 
*/
++        point_count += object->count;
++        if (point_count > max_points) {
++            DNP3SetEventTx(tx, DNP3_DECODER_EVENT_TOO_MANY_POINTS);
++            goto done;
++        }
++
+         int event = DNP3DecodeObject(header->group, header->variation, &buf,
+             &len, object->prefix_code, object->start, object->count,
+             object->points);
+@@ -1614,6 +1626,13 @@
+         if (ConfGetInt("app-layer.protocols.dnp3.max-tx", &value)) {
+             dnp3_max_tx = (uint64_t)value;
+         }
++
++        /* Parse max-points configuration. */
++        if (ConfGetInt("app-layer.protocols.dnp3.max-points", &value)) {
++            if (value > 0) {
++                max_points = (uint64_t)value;
++            }
++        }
+     } else {
+         SCLogConfig("Parser disabled for protocol %s. "
+             "Protocol detection still on.", proto_name);
+--- a/src/app-layer-dnp3.h
++++ b/src/app-layer-dnp3.h
+@@ -109,6 +109,7 @@
+     DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC,
+     DNP3_DECODER_EVENT_MALFORMED,
+     DNP3_DECODER_EVENT_UNKNOWN_OBJECT,
++    DNP3_DECODER_EVENT_TOO_MANY_POINTS,
+ };
+ 
+ /**
+--- a/suricata.yaml.in
++++ b/suricata.yaml.in
+@@ -1162,6 +1162,7 @@
+       detection-ports:
+         dp: 20000
+       #max-tx: 32
++      #max-points: 16384
+ 
+     # SCADA EtherNet/IP and CIP protocol support
+     enip:
diff -Nru suricata-7.0.10/debian/patches/CVE-2026-22259_4.patch 
suricata-7.0.10/debian/patches/CVE-2026-22259_4.patch
--- suricata-7.0.10/debian/patches/CVE-2026-22259_4.patch       1970-01-01 
01:00:00.000000000 +0100
+++ suricata-7.0.10/debian/patches/CVE-2026-22259_4.patch       2026-02-22 
13:21:42.000000000 +0100
@@ -0,0 +1,125 @@
+From a6d950315d9b6c1e35c10c24d9bb7128d422c21f Mon Sep 17 00:00:00 2001
+From: Jason Ish <[email protected]>
+Date: Tue, 6 Jan 2026 17:14:21 -0600
+Subject: [PATCH 4/4] dnp3: bound the maximum number of objects per tx
+
+Default to 2048, but provide a user configuration value.
+
+Ticket: #8181
+(cherry picked from commit 2c95f1ff44e17c3bc8693d5e23e175f2bc90ea10)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/a6d950315d9b6c1e35c10c24d9bb7128d422c21f.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8181
+Subject: Upstream fix for CVE-2026-22259 part 4
+---
+ doc/userguide/upgrade.rst |  9 +++++----
+ rules/dnp3-events.rules   |  5 +++++
+ src/app-layer-dnp3.c      | 18 ++++++++++++++++++
+ src/app-layer-dnp3.h      |  1 +
+ suricata.yaml.in          |  1 +
+ 5 files changed, 30 insertions(+), 4 deletions(-)
+
+--- a/doc/userguide/upgrade.rst
++++ b/doc/userguide/upgrade.rst
+@@ -40,10 +40,11 @@
+ Other Changes
+ ~~~~~~~~~~~~~
+ - ``dnp3`` has reduced the maximum number of open transactions from
+-  500 down to 32, and the maximum number of points per message from
+-  unbounded to 16384. Configuration options, ``max-tx`` and
+-  ``max-points`` have been added for users who may need to change
+-  these defaults.
++  500 down to 32, the maximum number of points per message from
++  unbounded to 16384, and the maximum number of objects per message
++  from unbounded to 2048. Configuration options, ``max-tx``,
++  ``max-points``, and ``max-objects`` have been added for users who
++  may need to change these defaults.
+ 
+ Upgrading to 7.0.9
+ ------------------
+--- a/rules/dnp3-events.rules
++++ b/rules/dnp3-events.rules
+@@ -29,3 +29,8 @@
+ alert dnp3 any any -> any any (msg:"SURICATA DNP3 Too many points in 
message"; \
+       app-layer-event:dnp3.too_many_points; \
+       classtype:protocol-command-decode; sid:2270005; rev:1;)
++
++# Too many objects.
++alert dnp3 any any -> any any (msg:"SURICATA DNP3 Too many objects"; \
++      app-layer-event:dnp3.too_many_objects; \
++      classtype:protocol-command-decode; sid:2270006; rev:1;)
+--- a/src/app-layer-dnp3.c
++++ b/src/app-layer-dnp3.c
+@@ -101,6 +101,9 @@
+ /* The maximum number of points allowed per message (configurable). */
+ static uint64_t max_points = 16384;
+ 
++/* The maximum number of objects allowed per message (configurable). */
++static uint64_t dnp3_max_objects = 2048;
++
+ /* Decoder event map. */
+ SCEnumCharMap dnp3_decoder_event_table[] = {
+     { "FLOODED", DNP3_DECODER_EVENT_FLOODED },
+@@ -110,6 +113,7 @@
+     { "MALFORMED", DNP3_DECODER_EVENT_MALFORMED },
+     { "UNKNOWN_OBJECT", DNP3_DECODER_EVENT_UNKNOWN_OBJECT },
+     { "TOO_MANY_POINTS", DNP3_DECODER_EVENT_TOO_MANY_POINTS },
++    { "TOO_MANY_OBJECTS", DNP3_DECODER_EVENT_TOO_MANY_OBJECTS },
+     { NULL, -1 },
+ };
+ 
+@@ -714,6 +718,7 @@
+ {
+     int retval = 0;
+     uint64_t point_count = 0;
++    uint64_t object_count = 0;
+ 
+     if (buf == NULL || len == 0) {
+         return 1;
+@@ -728,6 +733,12 @@
+         DNP3ObjHeader *header = (DNP3ObjHeader *)buf;
+         offset += sizeof(DNP3ObjHeader);
+ 
++        /* Check if we've exceeded the maximum number of objects. */
++        if (++object_count > dnp3_max_objects) {
++            DNP3SetEventTx(tx, DNP3_DECODER_EVENT_TOO_MANY_OBJECTS);
++            goto done;
++        }
++
+         DNP3Object *object = DNP3ObjectAlloc();
+         if (unlikely(object == NULL)) {
+             goto done;
+@@ -1633,6 +1644,13 @@
+                 max_points = (uint64_t)value;
+             }
+         }
++
++        /* Parse max-objects configuration. */
++        if (ConfGetInt("app-layer.protocols.dnp3.max-objects", &value)) {
++            if (value > 0) {
++                dnp3_max_objects = (uint64_t)value;
++            }
++        }
+     } else {
+         SCLogConfig("Parser disabled for protocol %s. "
+             "Protocol detection still on.", proto_name);
+--- a/src/app-layer-dnp3.h
++++ b/src/app-layer-dnp3.h
+@@ -110,6 +110,7 @@
+     DNP3_DECODER_EVENT_MALFORMED,
+     DNP3_DECODER_EVENT_UNKNOWN_OBJECT,
+     DNP3_DECODER_EVENT_TOO_MANY_POINTS,
++    DNP3_DECODER_EVENT_TOO_MANY_OBJECTS,
+ };
+ 
+ /**
+--- a/suricata.yaml.in
++++ b/suricata.yaml.in
+@@ -1163,6 +1163,7 @@
+         dp: 20000
+       #max-tx: 32
+       #max-points: 16384
++      #max-objects: 2048
+ 
+     # SCADA EtherNet/IP and CIP protocol support
+     enip:
diff -Nru suricata-7.0.10/debian/patches/CVE-2026-22261_1.patch 
suricata-7.0.10/debian/patches/CVE-2026-22261_1.patch
--- suricata-7.0.10/debian/patches/CVE-2026-22261_1.patch       1970-01-01 
01:00:00.000000000 +0100
+++ suricata-7.0.10/debian/patches/CVE-2026-22261_1.patch       2026-02-22 
13:21:42.000000000 +0100
@@ -0,0 +1,68 @@
+From 44d0c81f537f230e9215c769453fb4d7214217a1 Mon Sep 17 00:00:00 2001
+From: Philippe Antoine <[email protected]>
+Date: Tue, 9 Dec 2025 09:21:58 +0100
+Subject: [PATCH 1/2] output: optimize loop for finding alert http xff
+
+Ticket: 8156
+
+In case of non-tx alerts, we try to loop over all the txs to find
+the xff header. Do not start from tx_id 0, but from min_id
+as AppLayerParserTransactionsCleanup to skip txs that were freed
+
+(cherry picked from commit 3b1a6c1711b8f7d0bde4cb05f15cf50c751eda60)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/44d0c81f537f230e9215c769453fb4d7214217a1.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8156
+Subject: Upstream fix for CVE-2026-22261 part 1
+---
+ src/app-layer-htp-xff.c | 2 +-
+ src/app-layer-parser.c  | 7 +++++++
+ src/app-layer-parser.h  | 1 +
+ 3 files changed, 9 insertions(+), 1 deletion(-)
+
+diff --git a/src/app-layer-htp-xff.c b/src/app-layer-htp-xff.c
+index c145e5818..6eae5b1b3 100644
+--- a/src/app-layer-htp-xff.c
++++ b/src/app-layer-htp-xff.c
+@@ -180,7 +180,7 @@ int HttpXFFGetIPFromTx(const Flow *f, uint64_t tx_id, 
HttpXFFCfg *xff_cfg,
+ int HttpXFFGetIP(const Flow *f, HttpXFFCfg *xff_cfg, char *dstbuf, int 
dstbuflen)
+ {
+     HtpState *htp_state = NULL;
+-    uint64_t tx_id = 0;
++    uint64_t tx_id = AppLayerParserGetMinId(f->alparser);
+     uint64_t total_txs = 0;
+ 
+     htp_state = (HtpState *)FlowGetAppState(f);
+diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c
+index 627bf929a..2b8a59a14 100644
+--- a/src/app-layer-parser.c
++++ b/src/app-layer-parser.c
+@@ -721,6 +721,13 @@ uint64_t 
AppLayerParserGetTransactionLogId(AppLayerParserState *pstate)
+     SCReturnCT((pstate == NULL) ? 0 : pstate->log_id, "uint64_t");
+ }
+ 
++uint64_t AppLayerParserGetMinId(AppLayerParserState *pstate)
++{
++    SCEnter();
++
++    SCReturnCT((pstate == NULL) ? 0 : pstate->min_id, "uint64_t");
++}
++
+ void AppLayerParserSetTransactionLogId(AppLayerParserState *pstate, uint64_t 
tx_id)
+ {
+     SCEnter();
+diff --git a/src/app-layer-parser.h b/src/app-layer-parser.h
+index 77e8c813f..dad0b613c 100644
+--- a/src/app-layer-parser.h
++++ b/src/app-layer-parser.h
+@@ -230,6 +230,7 @@ void 
AppLayerParserDestroyProtocolParserLocalStorage(uint8_t ipproto, AppProto a
+ 
+ 
+ uint64_t AppLayerParserGetTransactionLogId(AppLayerParserState *pstate);
++uint64_t AppLayerParserGetMinId(AppLayerParserState *pstate);
+ void AppLayerParserSetTransactionLogId(AppLayerParserState *pstate, uint64_t 
tx_id);
+ 
+ uint64_t AppLayerParserGetTransactionInspectId(AppLayerParserState *pstate, 
uint8_t direction);
+-- 
+2.47.3
+
diff -Nru suricata-7.0.10/debian/patches/CVE-2026-22261_2.patch 
suricata-7.0.10/debian/patches/CVE-2026-22261_2.patch
--- suricata-7.0.10/debian/patches/CVE-2026-22261_2.patch       1970-01-01 
01:00:00.000000000 +0100
+++ suricata-7.0.10/debian/patches/CVE-2026-22261_2.patch       2026-02-22 
13:21:42.000000000 +0100
@@ -0,0 +1,128 @@
+From 7e704a3f50690b5f5d5cc573147ef41449fe37ac Mon Sep 17 00:00:00 2001
+From: Philippe Antoine <[email protected]>
+Date: Tue, 9 Dec 2025 09:38:31 +0100
+Subject: [PATCH 2/2] output: use tx iterator for finding alert http xff
+
+Ticket: 8156
+
+Allows better performance.
+
+(cherry picked from commit ab2e128176744ead5130707bb53fa59038e19634)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/7e704a3f50690b5f5d5cc573147ef41449fe37ac.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8156
+Subject: Upstream fix for CVE-2026-22261 part 2
+---
+ src/app-layer-htp-xff.c | 77 +++++++++++++++++++++++++----------------
+ 1 file changed, 47 insertions(+), 30 deletions(-)
+
+diff --git a/src/app-layer-htp-xff.c b/src/app-layer-htp-xff.c
+index 6eae5b1b3..2e5c25cdb 100644
+--- a/src/app-layer-htp-xff.c
++++ b/src/app-layer-htp-xff.c
+@@ -107,38 +107,12 @@ static int ParseXFFString(char *input, char *output, int 
output_size)
+     return 0;
+ }
+ 
+-/**
+- * \brief Function to return XFF IP if any in the selected transaction. The
+- * caller needs to lock the flow.
+- * \retval 1 if the IP has been found and returned in dstbuf
+- * \retval 0 if the IP has not being found or error
+- */
+-int HttpXFFGetIPFromTx(const Flow *f, uint64_t tx_id, HttpXFFCfg *xff_cfg,
+-        char *dstbuf, int dstbuflen)
++static int HttpXFFGetIPFromTxAux(
++        const Flow *f, htp_tx_t *tx, HttpXFFCfg *xff_cfg, char *dstbuf, int 
dstbuflen)
+ {
+     uint8_t xff_chain[XFF_CHAIN_MAXLEN];
+-    HtpState *htp_state = NULL;
+-    htp_tx_t *tx = NULL;
+-    uint64_t total_txs = 0;
+     uint8_t *p_xff = NULL;
+ 
+-    htp_state = (HtpState *)FlowGetAppState(f);
+-
+-    if (htp_state == NULL) {
+-        SCLogDebug("no http state, XFF IP cannot be retrieved");
+-        return 0;
+-    }
+-
+-    total_txs = AppLayerParserGetTxCnt(f, htp_state);
+-    if (tx_id >= total_txs)
+-        return 0;
+-
+-    tx = AppLayerParserGetTx(f->proto, ALPROTO_HTTP1, htp_state, tx_id);
+-    if (tx == NULL) {
+-        SCLogDebug("tx is NULL, XFF cannot be retrieved");
+-        return 0;
+-    }
+-
+     htp_header_t *h_xff = NULL;
+     if (tx->request_headers != NULL) {
+         h_xff = htp_table_get_c(tx->request_headers, xff_cfg->header);
+@@ -172,6 +146,38 @@ int HttpXFFGetIPFromTx(const Flow *f, uint64_t tx_id, 
HttpXFFCfg *xff_cfg,
+     return 0;
+ }
+ 
++/**
++ * \brief Function to return XFF IP if any in the selected transaction. The
++ * caller needs to lock the flow.
++ * \retval 1 if the IP has been found and returned in dstbuf
++ * \retval 0 if the IP has not being found or error
++ */
++int HttpXFFGetIPFromTx(
++        const Flow *f, uint64_t tx_id, HttpXFFCfg *xff_cfg, char *dstbuf, int 
dstbuflen)
++{
++    HtpState *htp_state = NULL;
++    uint64_t total_txs = 0;
++    htp_tx_t *tx = NULL;
++
++    htp_state = (HtpState *)FlowGetAppState(f);
++
++    if (htp_state == NULL) {
++        SCLogDebug("no http state, XFF IP cannot be retrieved");
++        return 0;
++    }
++
++    total_txs = AppLayerParserGetTxCnt(f, htp_state);
++    if (tx_id >= total_txs)
++        return 0;
++
++    tx = AppLayerParserGetTx(f->proto, ALPROTO_HTTP1, htp_state, tx_id);
++    if (tx == NULL) {
++        SCLogDebug("tx is NULL, XFF cannot be retrieved");
++        return 0;
++    }
++    return HttpXFFGetIPFromTxAux(f, tx, xff_cfg, dstbuf, dstbuflen);
++}
++
+ /**
+  *  \brief Function to return XFF IP if any. The caller needs to lock the 
flow.
+  *  \retval 1 if the IP has been found and returned in dstbuf
+@@ -190,9 +196,20 @@ int HttpXFFGetIP(const Flow *f, HttpXFFCfg *xff_cfg, char 
*dstbuf, int dstbuflen
+     }
+ 
+     total_txs = AppLayerParserGetTxCnt(f, htp_state);
+-    for (; tx_id < total_txs; tx_id++) {
+-        if (HttpXFFGetIPFromTx(f, tx_id, xff_cfg, dstbuf, dstbuflen) == 1)
++    AppLayerGetTxIteratorFunc IterFunc = AppLayerGetTxIterator(f->proto, 
f->alproto);
++    AppLayerGetTxIterState state;
++    memset(&state, 0, sizeof(state));
++
++    while (1) {
++        AppLayerGetTxIterTuple ires =
++                IterFunc(f->proto, f->alproto, f->alstate, tx_id, total_txs, 
&state);
++        if (ires.tx_ptr == NULL)
++            break;
++
++        if (HttpXFFGetIPFromTxAux(f, ires.tx_ptr, xff_cfg, dstbuf, dstbuflen) 
== 1)
+             return 1;
++
++        tx_id = ires.tx_id + 1;
+     }
+ 
+ end:
+-- 
+2.47.3
+
diff -Nru suricata-7.0.10/debian/patches/CVE-2026-22262_1.patch 
suricata-7.0.10/debian/patches/CVE-2026-22262_1.patch
--- suricata-7.0.10/debian/patches/CVE-2026-22262_1.patch       1970-01-01 
01:00:00.000000000 +0100
+++ suricata-7.0.10/debian/patches/CVE-2026-22262_1.patch       2026-02-22 
13:21:42.000000000 +0100
@@ -0,0 +1,42 @@
+From 32609e6896f9079c175665a94005417cec7637eb Mon Sep 17 00:00:00 2001
+From: Philippe Antoine <[email protected]>
+Date: Mon, 17 Nov 2025 13:27:54 +0100
+Subject: [PATCH 1/2] datasets: explicitly errors on too long string
+
+Also avoids stack allocation
+
+Ticket: 8110
+(cherry picked from commit 0eff24213763c2aa2bb0957901d5dc1e18414dbf)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/32609e6896f9079c175665a94005417cec7637eb.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8110
+Subject: Upstream fix for CVE-2026-22262 part 1
+---
+ src/datasets-string.c | 9 +++++----
+ 1 file changed, 5 insertions(+), 4 deletions(-)
+
+diff --git a/src/datasets-string.c b/src/datasets-string.c
+index 0a8f499ae..524a60ad9 100644
+--- a/src/datasets-string.c
++++ b/src/datasets-string.c
+@@ -49,12 +49,13 @@ int StringAsBase64(const void *s, char *out, size_t 
out_size)
+     const StringType *str = s;
+ 
+     unsigned long len = Base64EncodeBufferSize(str->len);
+-    uint8_t encoded_data[len];
+-    if (Base64Encode((unsigned char *)str->ptr, str->len,
+-        encoded_data, &len) != SC_BASE64_OK)
++    if (len + 2 > out_size) {
++        // linefeed and final zero
++        return 0;
++    }
++    if (Base64Encode((unsigned char *)str->ptr, str->len, (uint8_t *)out, 
&len) != SC_BASE64_OK)
+         return 0;
+ 
+-    strlcpy(out, (const char *)encoded_data, out_size);
+     strlcat(out, "\n", out_size);
+     return strlen(out);
+ }
+-- 
+2.47.3
+
diff -Nru suricata-7.0.10/debian/patches/CVE-2026-22262_2.patch 
suricata-7.0.10/debian/patches/CVE-2026-22262_2.patch
--- suricata-7.0.10/debian/patches/CVE-2026-22262_2.patch       1970-01-01 
01:00:00.000000000 +0100
+++ suricata-7.0.10/debian/patches/CVE-2026-22262_2.patch       2026-02-22 
13:21:42.000000000 +0100
@@ -0,0 +1,66 @@
+From 27a2180bceaa3477419c78c54fce364398d011f1 Mon Sep 17 00:00:00 2001
+From: Philippe Antoine <[email protected]>
+Date: Tue, 25 Nov 2025 14:43:18 +0100
+Subject: [PATCH 2/2] datasets: allocates on the heap if string base64 is long
+
+Ticket: 8110
+(cherry picked from commit d6bc718e303ecbec5999066b8bc88eeeca743658)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/27a2180bceaa3477419c78c54fce364398d011f1.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8110
+Subject: Upstream fix for CVE-2026-22262 part 2
+---
+ src/datasets-string.c |  4 ++--
+ src/util-thash.c      | 21 ++++++++++++++++++++-
+ 2 files changed, 22 insertions(+), 3 deletions(-)
+
+diff --git a/src/datasets-string.c b/src/datasets-string.c
+index 524a60ad9..53a179a10 100644
+--- a/src/datasets-string.c
++++ b/src/datasets-string.c
+@@ -50,8 +50,8 @@ int StringAsBase64(const void *s, char *out, size_t out_size)
+ 
+     unsigned long len = Base64EncodeBufferSize(str->len);
+     if (len + 2 > out_size) {
+-        // linefeed and final zero
+-        return 0;
++        // linefeed and final zero : signal we need more space
++        return len + 2;
+     }
+     if (Base64Encode((unsigned char *)str->ptr, str->len, (uint8_t *)out, 
&len) != SC_BASE64_OK)
+         return 0;
+diff --git a/src/util-thash.c b/src/util-thash.c
+index 548637916..c6df02cf3 100644
+--- a/src/util-thash.c
++++ b/src/util-thash.c
+@@ -390,7 +390,26 @@ int THashWalk(THashTableContext *ctx, THashFormatFunc 
FormatterFunc, THashOutput
+             char output_string[1024] = "";
+             int size = FormatterFunc(h->data, output_string, 
sizeof(output_string));
+             if (size > 0) {
+-                if (OutputterFunc(output_ctx, (const uint8_t *)output_string, 
size) < 0) {
++                if (size > 1024) {
++                    // we did not provide enough space on the stack, let's 
allocate on the heap
++                    char *out_alloc = SCCalloc(1, size);
++                    if (out_alloc == NULL) {
++                        err = true;
++                        break;
++                    }
++                    size = FormatterFunc(h->data, out_alloc, size);
++                    if (size == 0) {
++                        err = true;
++                        SCFree(out_alloc);
++                        break;
++                    }
++                    if (OutputterFunc(output_ctx, (const uint8_t *)out_alloc, 
size) < 0) {
++                        err = true;
++                        SCFree(out_alloc);
++                        break;
++                    }
++                    SCFree(out_alloc);
++                } else if (OutputterFunc(output_ctx, (const uint8_t 
*)output_string, size) < 0) {
+                     err = true;
+                     break;
+                 }
+-- 
+2.47.3
+
diff -Nru suricata-7.0.10/debian/patches/CVE-2026-22264.patch 
suricata-7.0.10/debian/patches/CVE-2026-22264.patch
--- suricata-7.0.10/debian/patches/CVE-2026-22264.patch 1970-01-01 
01:00:00.000000000 +0100
+++ suricata-7.0.10/debian/patches/CVE-2026-22264.patch 2026-02-22 
13:21:42.000000000 +0100
@@ -0,0 +1,84 @@
+From 5789a3d3760dbf33d93fc56c27bd9529e5bdc8f2 Mon Sep 17 00:00:00 2001
+From: Shivani Bhardwaj <[email protected]>
+Date: Mon, 5 Jan 2026 19:27:11 +0530
+Subject: [PATCH] detect/alert: check alert queue capacity before expanding
+
+So far, the alert queue was expanded by doubling in size w/o any
+boundary checks in place. This led to situations where doubling
+the alert_queue_capacity meant overflow of the very same value
+stored in det_ctx.
+This led to heap-use-after-free in some conditions where
+det_ctx->alert_queue_capacity overflowed.
+
+Fix this by capping the max of alert_queue_capacity by checking if its
+expansion could result in an overflow.
+
+Security 8190
+
+(cherry picked from commit ac1eb394181530430fb7262969f423a1bf8f209b)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/5789a3d3760dbf33d93fc56c27bd9529e5bdc8f2.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8190
+Subject: Upstream fix for CVE-2026-22264
+---
+ src/detect-engine-alert.c | 35 +++++++++++++++++++++++++----------
+ 1 file changed, 25 insertions(+), 10 deletions(-)
+
+diff --git a/src/detect-engine-alert.c b/src/detect-engine-alert.c
+index f3e49586e..0cce98196 100644
+--- a/src/detect-engine-alert.c
++++ b/src/detect-engine-alert.c
+@@ -238,6 +238,22 @@ void AlertQueueFree(DetectEngineThreadCtx *det_ctx)
+     det_ctx->alert_queue_capacity = 0;
+ }
+ 
++static inline uint16_t AlertQueueExpandDo(DetectEngineThreadCtx *det_ctx, 
uint16_t new_cap)
++{
++    DEBUG_VALIDATE_BUG_ON(det_ctx->alert_queue_capacity >= new_cap);
++
++    void *tmp_queue = SCRealloc(det_ctx->alert_queue, new_cap * 
sizeof(PacketAlert));
++    if (unlikely(tmp_queue == NULL)) {
++        /* queue capacity didn't change */
++        return det_ctx->alert_queue_capacity;
++    }
++    det_ctx->alert_queue = tmp_queue;
++    det_ctx->alert_queue_capacity = new_cap;
++    SCLogDebug("Alert queue size expanded: %u elements, bytes: %" PRIuMAX "",
++            det_ctx->alert_queue_capacity, (uintmax_t)(new_cap * 
sizeof(PacketAlert)));
++    return new_cap;
++}
++
+ /** \internal
+  * \retval the new capacity
+  */
+@@ -247,18 +263,17 @@ static uint16_t AlertQueueExpand(DetectEngineThreadCtx 
*det_ctx)
+     if (unlikely(g_eps_is_alert_queue_fail_mode))
+         return det_ctx->alert_queue_capacity;
+ #endif
+-    uint16_t new_cap = det_ctx->alert_queue_capacity * 2;
+-    void *tmp_queue = SCRealloc(det_ctx->alert_queue, 
(size_t)(sizeof(PacketAlert) * new_cap));
+-    if (unlikely(tmp_queue == NULL)) {
+-        /* queue capacity didn't change */
++    if (det_ctx->alert_queue_capacity == UINT16_MAX) {
+         return det_ctx->alert_queue_capacity;
+     }
+-    det_ctx->alert_queue = tmp_queue;
+-    det_ctx->alert_queue_capacity = new_cap;
+-    SCLogDebug("Alert queue size doubled: %u elements, bytes: %" PRIuMAX "",
+-            det_ctx->alert_queue_capacity,
+-            (uintmax_t)(sizeof(PacketAlert) * det_ctx->alert_queue_capacity));
+-    return new_cap;
++
++    uint16_t new_cap;
++    if (det_ctx->alert_queue_capacity > (UINT16_MAX / 2)) {
++        new_cap = UINT16_MAX;
++    } else {
++        new_cap = det_ctx->alert_queue_capacity * 2;
++    }
++    return AlertQueueExpandDo(det_ctx, new_cap);
+ }
+ 
+ /** \internal
+-- 
+2.47.3
+
diff -Nru suricata-7.0.10/debian/patches/series 
suricata-7.0.10/debian/patches/series
--- suricata-7.0.10/debian/patches/series       2025-12-10 20:12:20.000000000 
+0100
+++ suricata-7.0.10/debian/patches/series       2026-02-22 13:21:42.000000000 
+0100
@@ -15,3 +15,15 @@
 CVE-2025-64332.patch
 CVE-2025-64331.patch
 CVE-2025-64330.patch
+CVE-2026-22258_1.patch
+CVE-2026-22258_2.patch
+CVE-2026-22258_3.patch
+CVE-2026-22262_1.patch
+CVE-2026-22262_2.patch
+CVE-2026-22264.patch
+CVE-2026-22259_1.patch
+CVE-2026-22259_2.patch
+CVE-2026-22259_3.patch
+CVE-2026-22259_4.patch
+CVE-2026-22261_1.patch
+CVE-2026-22261_2.patch
diff --git a/debian/changelog b/debian/changelog
index 13814f8a..6a7e9319 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,13 @@
+suricata (1:7.0.10-1+deb13u3) UNRELEASED; urgency=high
+
+  * Fix CVE-2026-22258 in 7.0.10.
+    Cherry-Picked from:
+    * f82a388d0283725cb76782cf64e8341cab370830
+    * df389f8a43a06c718bb336ea082d6c80d6fefda0
+    * c9b80e5affe073ce9d95d0c935a8d67647c83bf7
+
+ -- Andreas Dolp <[email protected]>  Thu, 15 Jan 2026 20:59:38 +0100
+
 suricata (1:7.0.10-1+deb13u2) trixie; urgency=medium
 
   * Fix CVE-2025-64344 in 7.0.10.
diff --git a/debian/patches/CVE-2026-22258_1.patch 
b/debian/patches/CVE-2026-22258_1.patch
new file mode 100644
index 00000000..719b096b
--- /dev/null
+++ b/debian/patches/CVE-2026-22258_1.patch
@@ -0,0 +1,286 @@
+From f82a388d0283725cb76782cf64e8341cab370830 Mon Sep 17 00:00:00 2001
+From: Shivani Bhardwaj <[email protected]>
+Date: Tue, 6 Jan 2026 16:44:52 +0530
+Subject: [PATCH 1/3] dcerpc: add upper limit on stub data
+
+DCERPC parsers had no upper bounds when it came to extending the stub
+data buffer. Traffic can be crafted to bypass some internal parser
+conditions to create an indefinite buffering in the stub_data array that
+can make Suricata crash.
+
+Add a default limit of 1MiB and make it configurable for the user.
+
+Security 8182
+
+Co-authored-by: Philippe Antoine <[email protected]>
+(cherry picked from commit e412215af990feeffbb66c7dd9f392813a20ae50)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/f82a388d0283725cb76782cf64e8341cab370830.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8182
+Subject: Upstream fix for CVE-2026-22258 part 1
+---
+ rust/src/dcerpc/dcerpc.rs     | 31 +++++++++++++++++++++++++++++--
+ rust/src/dcerpc/dcerpc_udp.rs | 18 +++++++++++++-----
+ rust/src/smb/dcerpc.rs        | 31 +++++++++++++++++++++++--------
+ rust/src/smb/smb.rs           | 23 +++++++++++++++++++++++
+ suricata.yaml.in              |  4 ++++
+ 5 files changed, 92 insertions(+), 15 deletions(-)
+
+diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs
+index 5469b736b..7297ff82e 100644
+--- a/rust/src/dcerpc/dcerpc.rs
++++ b/rust/src/dcerpc/dcerpc.rs
+@@ -25,7 +25,9 @@ use std;
+ use std::cmp;
+ use std::ffi::CString;
+ use std::collections::VecDeque;
+-use crate::conf::conf_get;
++use crate::conf::{conf_get, get_memval};
++
++pub static mut DCERPC_MAX_STUB_SIZE: u32 = 1048576;
+ 
+ // Constant DCERPC UDP Header length
+ pub const DCERPC_HDR_LEN: u16 = 16;
+@@ -163,6 +165,11 @@ pub fn get_req_type_for_resp(t: u8) -> u8 {
+         _ => DCERPC_TYPE_UNKNOWN,
+     }
+ }
++#[inline(always)]
++pub fn cfg_max_stub_size() -> u32 {
++    unsafe { DCERPC_MAX_STUB_SIZE }
++}
++
+ 
+ #[derive(Default, Debug)]
+ pub struct DCERPCTransaction {
+@@ -1096,7 +1103,12 @@ fn evaluate_stub_params(
+     }
+ 
+     let input_slice = &input[..stub_len as usize];
+-    stub_data_buffer.extend_from_slice(input_slice);
++    let max_size = cfg_max_stub_size() as usize;
++    if (stub_data_buffer.len() + input_slice.len()) < max_size {
++        stub_data_buffer.extend_from_slice(input_slice);
++    } else if stub_data_buffer.len() < max_size {
++        stub_data_buffer.extend_from_slice(&input_slice[..max_size - 
stub_data_buffer.len()]);
++    }
+ 
+     stub_len
+ }
+@@ -1396,6 +1408,21 @@ pub unsafe extern "C" fn rs_dcerpc_register_parser() {
+             }
+         }
+         SCLogDebug!("Rust DCERPC parser registered.");
++        let retval = conf_get("app-layer.protocols.dcerpc.max-stub-size");
++        if let Some(val) = retval {
++            match get_memval(val) {
++                Ok(retval) => {
++                    if retval > 0 {
++                        DCERPC_MAX_STUB_SIZE = retval as u32;
++                    } else {
++                        SCLogError!("Invalid max-stub-size value");
++                    }
++                }
++                Err(_) => {
++                    SCLogError!("Invalid max-stub-size value");
++                }
++            }
++        }
+     } else {
+         SCLogDebug!("Protocol detector and parser disabled for DCERPC.");
+     }
+diff --git a/rust/src/dcerpc/dcerpc_udp.rs b/rust/src/dcerpc/dcerpc_udp.rs
+index d70ca1b53..0a6213a87 100644
+--- a/rust/src/dcerpc/dcerpc_udp.rs
++++ b/rust/src/dcerpc/dcerpc_udp.rs
+@@ -1,4 +1,4 @@
+-/* Copyright (C) 2020 Open Information Security Foundation
++/* Copyright (C) 2020-2026 Open Information Security Foundation
+  *
+  * You can copy, redistribute or modify this Program under the terms of
+  * the GNU General Public License version 2 as published by the Free
+@@ -19,7 +19,7 @@ use crate::applayer::{self, *};
+ use crate::core::{self, Direction, DIR_BOTH};
+ use crate::dcerpc::dcerpc::{
+     DCERPCTransaction, DCERPC_MAX_TX, DCERPC_TYPE_REQUEST, 
DCERPC_TYPE_RESPONSE, PFCL1_FRAG, PFCL1_LASTFRAG,
+-    rs_dcerpc_get_alstate_progress, ALPROTO_DCERPC, PARSER_NAME,
++    rs_dcerpc_get_alstate_progress, ALPROTO_DCERPC, PARSER_NAME, 
cfg_max_stub_size,
+ };
+ use nom7::Err;
+ use std;
+@@ -169,18 +169,27 @@ impl DCERPCUDPState {
+             tx.tx_data.updated_ts = true;
+             let done = (hdr.flags1 & PFCL1_FRAG) == 0 || (hdr.flags1 & 
PFCL1_LASTFRAG) != 0;
+ 
++            let max_size = cfg_max_stub_size() as usize;
+             match hdr.pkt_type {
+                 DCERPC_TYPE_REQUEST => {
+-                    tx.stub_data_buffer_ts.extend_from_slice(input);
+                     tx.frag_cnt_ts += 1;
++                    if input.len() + tx.stub_data_buffer_ts.len() < max_size {
++                        tx.stub_data_buffer_ts.extend_from_slice(input);
++                    } else if tx.stub_data_buffer_ts.len() < max_size {
++                        
tx.stub_data_buffer_ts.extend_from_slice(&input[..max_size - 
tx.stub_data_buffer_ts.len()]);
++                    }
+                     if done {
+                         tx.req_done = true;
+                     }
+                     return true;
+                 }
+                 DCERPC_TYPE_RESPONSE => {
+-                    tx.stub_data_buffer_tc.extend_from_slice(input);
+                     tx.frag_cnt_tc += 1;
++                    if input.len() + tx.stub_data_buffer_tc.len() < max_size {
++                        tx.stub_data_buffer_tc.extend_from_slice(input);
++                    } else if tx.stub_data_buffer_tc.len() < max_size {
++                        
tx.stub_data_buffer_tc.extend_from_slice(&input[..max_size - 
tx.stub_data_buffer_tc.len()]);
++                    }
+                     if done {
+                         tx.resp_done = true;
+                     }
+@@ -397,7 +406,6 @@ pub unsafe extern "C" fn rs_dcerpc_udp_register_parser() {
+     }
+ }
+ 
+-
+ #[cfg(test)]
+ mod tests {
+     use crate::applayer::AppLayerResult;
+diff --git a/rust/src/smb/dcerpc.rs b/rust/src/smb/dcerpc.rs
+index 6c2a2f934..1e62241bb 100644
+--- a/rust/src/smb/dcerpc.rs
++++ b/rust/src/smb/dcerpc.rs
+@@ -18,7 +18,7 @@
+ // written by Victor Julien
+ 
+ use uuid;
+-use crate::smb::smb::*;
++use crate::smb::smb::{cfg_max_stub_size, *};
+ use crate::smb::smb2::*;
+ use crate::smb::dcerpc_records::*;
+ use crate::smb::events::*;
+@@ -205,10 +205,15 @@ pub fn smb_write_dcerpc_record(state: &mut SMBState,
+                                 SCLogDebug!("previous CMD {} found at tx {} 
=> {:?}",
+                                         dcer.packet_type, tx.id, tx);
+                                 if let 
Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data {
+-                                    SCLogDebug!("additional frag of size {}", 
recr.data.len());
+-                                    
tdn.stub_data_ts.extend_from_slice(recr.data);
+                                     tdn.frag_cnt_ts += 1;
+-                                    SCLogDebug!("stub_data now {}", 
tdn.stub_data_ts.len());
++                                    let max_size = cfg_max_stub_size() as 
usize;
++                                    if recr.data.len() + 
tdn.stub_data_ts.len() < max_size {
++                                        SCLogDebug!("additional frag of size 
{}", recr.data.len());
++                                        
tdn.stub_data_ts.extend_from_slice(recr.data);
++                                        SCLogDebug!("stub_data now {}", 
tdn.stub_data_ts.len());
++                                    } else if tdn.stub_data_ts.len() < 
max_size {
++                                        
tdn.stub_data_ts.extend_from_slice(&recr.data[..max_size - 
tdn.stub_data_ts.len()]);
++                                    }
+                                 }
+                                 if dcer.last_frag {
+                                     SCLogDebug!("last frag set, so request 
side of DCERPC closed");
+@@ -240,12 +245,17 @@ pub fn smb_write_dcerpc_record(state: &mut SMBState,
+                             SCLogDebug!("DCERPC: REQUEST {:?}", recr);
+                             if let Some(SMBTransactionTypeData::DCERPC(ref 
mut tdn)) = tx.type_data {
+                                 SCLogDebug!("first frag size {}", 
recr.data.len());
+-                                tdn.stub_data_ts.extend_from_slice(recr.data);
+                                 tdn.opnum = recr.opnum;
+                                 tdn.context_id = recr.context_id;
+                                 tdn.frag_cnt_ts += 1;
+-                                SCLogDebug!("DCERPC: REQUEST opnum {} stub 
data len {}",
+-                                        tdn.opnum, tdn.stub_data_ts.len());
++                                let max_size = cfg_max_stub_size() as usize;
++                                if tdn.stub_data_ts.len() + recr.data.len() < 
max_size {
++                                    
tdn.stub_data_ts.extend_from_slice(recr.data);
++                                    SCLogDebug!("DCERPC: REQUEST opnum {} 
stub data len {}",
++                                            tdn.opnum, 
tdn.stub_data_ts.len());
++                                } else if tdn.stub_data_ts.len() < max_size {
++                                    
tdn.stub_data_ts.extend_from_slice(&recr.data[..max_size - 
tdn.stub_data_ts.len()]);
++                                }
+                             }
+                             if dcer.last_frag {
+                                 tx.request_done = true;
+@@ -407,8 +417,13 @@ fn dcerpc_response_handle(tx: &mut SMBTransaction,
+                     if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) 
= tx.type_data {
+                         SCLogDebug!("CMD 11 found at tx {}", tx.id);
+                         tdn.set_result(DCERPC_TYPE_RESPONSE);
+-                        tdn.stub_data_tc.extend_from_slice(respr.data);
++                        let max_size = cfg_max_stub_size() as usize;
+                         tdn.frag_cnt_tc += 1;
++                        if tdn.stub_data_tc.len() + respr.data.len() < 
max_size {
++                            tdn.stub_data_tc.extend_from_slice(respr.data);
++                        } else if tdn.stub_data_tc.len() < max_size {
++                            
tdn.stub_data_tc.extend_from_slice(&respr.data[..max_size - 
tdn.stub_data_tc.len()]);
++                        }
+                     }
+                     tx.vercmd.set_ntstatus(ntstatus);
+                     tx.response_done = dcer.last_frag;
+diff --git a/rust/src/smb/smb.rs b/rust/src/smb/smb.rs
+index c22bc9fc9..0a0fc1082 100644
+--- a/rust/src/smb/smb.rs
++++ b/rust/src/smb/smb.rs
+@@ -81,6 +81,8 @@ pub static mut SMB_CFG_MAX_WRITE_SIZE: u32 = 16777216;
+ pub static mut SMB_CFG_MAX_WRITE_QUEUE_SIZE: u32 = 67108864;
+ pub static mut SMB_CFG_MAX_WRITE_QUEUE_CNT: u32 = 64;
+ 
++pub static mut SMB_DCERPC_MAX_STUB_SIZE: u32 = 1048576;
++
+ static mut ALPROTO_SMB: AppProto = ALPROTO_UNKNOWN;
+ 
+ static mut SMB_MAX_TX: usize = 1024;
+@@ -2438,6 +2440,21 @@ pub unsafe extern "C" fn rs_smb_register_parser() {
+                 SCLogError!("Invalid value for smb.max-tx");
+             }
+         }
++       let retval = conf_get("app-layer.protocols.smb.dcerpc.max-stub-size");
++       if let Some(val) = retval {
++           match get_memval(val) {
++               Ok(retval) => {
++                    if retval > 0 {
++                        SMB_DCERPC_MAX_STUB_SIZE = retval as u32;
++                    } else {
++                        SCLogError!("Invalid max-stub-size value");
++                    }
++               }
++               Err(_) => {
++                    SCLogError!("Invalid max-stub-size value");
++               }
++           }
++       }
+         SCLogConfig!("read: max record size: {}, max queued chunks {}, max 
queued size {}",
+                 SMB_CFG_MAX_READ_SIZE, SMB_CFG_MAX_READ_QUEUE_CNT, 
SMB_CFG_MAX_READ_QUEUE_SIZE);
+         SCLogConfig!("write: max record size: {}, max queued chunks {}, max 
queued size {}",
+@@ -2446,3 +2463,9 @@ pub unsafe extern "C" fn rs_smb_register_parser() {
+         SCLogDebug!("Protocol detector and parser disabled for SMB.");
+     }
+ }
++
++#[inline(always)]
++pub fn cfg_max_stub_size() -> u32 {
++    unsafe { SMB_DCERPC_MAX_STUB_SIZE }
++}
++
+diff --git a/suricata.yaml.in b/suricata.yaml.in
+index 7640f2b62..eab6ca500 100644
+--- a/suricata.yaml.in
++++ b/suricata.yaml.in
+@@ -951,6 +951,8 @@ app-layer:
+       enabled: yes
+       # Maximum number of live DCERPC transactions per flow
+       # max-tx: 1024
++      #max-stub-size: 1MiB
++
+     ftp:
+       enabled: yes
+       # memcap: 64mb
+@@ -1015,6 +1017,8 @@ app-layer:
+ 
+       # Stream reassembly size for SMB streams. By default track it 
completely.
+       #stream-depth: 0
++      #dcerpc:
++      #  max-stub-size: 1MiB
+ 
+     nfs:
+       enabled: yes
+-- 
+2.47.3
+
diff --git a/debian/patches/CVE-2026-22258_2.patch 
b/debian/patches/CVE-2026-22258_2.patch
new file mode 100644
index 00000000..664ba2e9
--- /dev/null
+++ b/debian/patches/CVE-2026-22258_2.patch
@@ -0,0 +1,54 @@
+From df389f8a43a06c718bb336ea082d6c80d6fefda0 Mon Sep 17 00:00:00 2001
+From: Shivani Bhardwaj <[email protected]>
+Date: Wed, 7 Jan 2026 10:33:57 +0530
+Subject: [PATCH 2/3] doc: add dcerpc.max-stub-size config param
+
+(cherry picked from commit 6702791a9c4463858c8b54ee8678fd4f5fbe831a)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/df389f8a43a06c718bb336ea082d6c80d6fefda0.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8182
+Subject: Upstream fix for CVE-2026-22258 part 2
+---
+ doc/userguide/configuration/suricata-yaml.rst | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+diff --git a/doc/userguide/configuration/suricata-yaml.rst 
b/doc/userguide/configuration/suricata-yaml.rst
+index bd414fbc6..8ea093b51 100644
+--- a/doc/userguide/configuration/suricata-yaml.rst
++++ b/doc/userguide/configuration/suricata-yaml.rst
+@@ -1674,6 +1674,9 @@ Several options are available for limiting record sizes 
and data chunk tracking.
+       max-write-queue-size: 16mb
+       max-write-queue-cnt: 16
+ 
++      dcerpc:
++        max-stub-size: 1MiB
++
+ The `max-read-size` option can be set to control the max size of accepted
+ READ records. Events will be raised if a READ request asks for too much data
+ and/or if READ responses are too big. A value of 0 disables the checks.
+@@ -1685,6 +1688,8 @@ data. A value of 0 disables the checks.
+ Additionally if the `max-read-size` or `max-write-size` values in the
+ "negotiate protocol response" exceeds this limit an event will also be raised.
+ 
++To control the size of the DCERPC stub data, `dcerpc.max-stub-size` should be
++used. It is by default set to 1MiB.
+ 
+ For file tracking, extraction and file data inspection the parser queues up
+ out of order data chunks for both READs and WRITEs. To avoid using too much
+@@ -1710,6 +1715,13 @@ the limits are exceeded, and an event will be raised.
+ `max-write-queue-size` and `max-write-queue-cnt` are as the READ variants,
+ but then for WRITEs.
+ 
++Configure DCERPC
++~~~~~~~~~~~~~~~~
++
++DCERPC has one parameter that can be customized.
++`max-stub-size` is used to control the stub data size of a DCERPC 
request/response. By
++default, it is set to 1MiB.
++
+ Configure HTTP2
+ ~~~~~~~~~~~~~~~
+ 
+-- 
+2.47.3
+
diff --git a/debian/patches/CVE-2026-22258_3.patch 
b/debian/patches/CVE-2026-22258_3.patch
new file mode 100644
index 00000000..1f0bcfe2
--- /dev/null
+++ b/debian/patches/CVE-2026-22258_3.patch
@@ -0,0 +1,73 @@
+From c9b80e5affe073ce9d95d0c935a8d67647c83bf7 Mon Sep 17 00:00:00 2001
+From: Philippe Antoine <[email protected]>
+Date: Thu, 8 Jan 2026 14:48:40 +0100
+Subject: [PATCH 3/3] dcerpc: use saturating_add to count fragments
+
+And do not overflow if we have traffic with more than 65K fragments
+
+(cherry picked from commit a48200b9e5befb1f0aa45ad5b33e2664e6a9fa41)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/c9b80e5affe073ce9d95d0c935a8d67647c83bf7.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8182
+Subject: Upstream fix for CVE-2026-22258 part 3
+---
+ rust/src/dcerpc/dcerpc_udp.rs | 4 ++--
+ rust/src/smb/dcerpc.rs        | 6 +++---
+ 2 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/rust/src/dcerpc/dcerpc_udp.rs b/rust/src/dcerpc/dcerpc_udp.rs
+index 0a6213a87..87fa9764b 100644
+--- a/rust/src/dcerpc/dcerpc_udp.rs
++++ b/rust/src/dcerpc/dcerpc_udp.rs
+@@ -172,7 +172,7 @@ impl DCERPCUDPState {
+             let max_size = cfg_max_stub_size() as usize;
+             match hdr.pkt_type {
+                 DCERPC_TYPE_REQUEST => {
+-                    tx.frag_cnt_ts += 1;
++                    tx.frag_cnt_ts = tx.frag_cnt_ts.saturating_add(1);
+                     if input.len() + tx.stub_data_buffer_ts.len() < max_size {
+                         tx.stub_data_buffer_ts.extend_from_slice(input);
+                     } else if tx.stub_data_buffer_ts.len() < max_size {
+@@ -184,7 +184,7 @@ impl DCERPCUDPState {
+                     return true;
+                 }
+                 DCERPC_TYPE_RESPONSE => {
+-                    tx.frag_cnt_tc += 1;
++                    tx.frag_cnt_tc = tx.frag_cnt_tc.saturating_add(1);
+                     if input.len() + tx.stub_data_buffer_tc.len() < max_size {
+                         tx.stub_data_buffer_tc.extend_from_slice(input);
+                     } else if tx.stub_data_buffer_tc.len() < max_size {
+diff --git a/rust/src/smb/dcerpc.rs b/rust/src/smb/dcerpc.rs
+index 1e62241bb..5cb1adeba 100644
+--- a/rust/src/smb/dcerpc.rs
++++ b/rust/src/smb/dcerpc.rs
+@@ -205,7 +205,7 @@ pub fn smb_write_dcerpc_record(state: &mut SMBState,
+                                 SCLogDebug!("previous CMD {} found at tx {} 
=> {:?}",
+                                         dcer.packet_type, tx.id, tx);
+                                 if let 
Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data {
+-                                    tdn.frag_cnt_ts += 1;
++                                    tdn.frag_cnt_ts = 
tdn.frag_cnt_ts.saturating_add(1);
+                                     let max_size = cfg_max_stub_size() as 
usize;
+                                     if recr.data.len() + 
tdn.stub_data_ts.len() < max_size {
+                                         SCLogDebug!("additional frag of size 
{}", recr.data.len());
+@@ -247,7 +247,7 @@ pub fn smb_write_dcerpc_record(state: &mut SMBState,
+                                 SCLogDebug!("first frag size {}", 
recr.data.len());
+                                 tdn.opnum = recr.opnum;
+                                 tdn.context_id = recr.context_id;
+-                                tdn.frag_cnt_ts += 1;
++                                tdn.frag_cnt_ts = 
tdn.frag_cnt_ts.saturating_add(1);
+                                 let max_size = cfg_max_stub_size() as usize;
+                                 if tdn.stub_data_ts.len() + recr.data.len() < 
max_size {
+                                     
tdn.stub_data_ts.extend_from_slice(recr.data);
+@@ -418,7 +418,7 @@ fn dcerpc_response_handle(tx: &mut SMBTransaction,
+                         SCLogDebug!("CMD 11 found at tx {}", tx.id);
+                         tdn.set_result(DCERPC_TYPE_RESPONSE);
+                         let max_size = cfg_max_stub_size() as usize;
+-                        tdn.frag_cnt_tc += 1;
++                        tdn.frag_cnt_tc = tdn.frag_cnt_tc.saturating_add(1);
+                         if tdn.stub_data_tc.len() + respr.data.len() < 
max_size {
+                             tdn.stub_data_tc.extend_from_slice(respr.data);
+                         } else if tdn.stub_data_tc.len() < max_size {
+-- 
+2.47.3
+
diff --git a/debian/patches/series b/debian/patches/series
index bf99f825..8e7dfe52 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -15,3 +15,6 @@ CVE-2025-64333.patch
 CVE-2025-64332.patch
 CVE-2025-64331.patch
 CVE-2025-64330.patch
+CVE-2026-22258_1.patch
+CVE-2026-22258_2.patch
+CVE-2026-22258_3.patch
diff --git a/debian/changelog b/debian/changelog
index 6a7e9319..df66aceb 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -5,6 +5,10 @@ suricata (1:7.0.10-1+deb13u3) UNRELEASED; urgency=high
     * f82a388d0283725cb76782cf64e8341cab370830
     * df389f8a43a06c718bb336ea082d6c80d6fefda0
     * c9b80e5affe073ce9d95d0c935a8d67647c83bf7
+  * Fix CVE-2026-22262 in 7.0.10.
+    Cherry-Picked from:
+    * 32609e6896f9079c175665a94005417cec7637eb
+    * 27a2180bceaa3477419c78c54fce364398d011f1
 
  -- Andreas Dolp <[email protected]>  Thu, 15 Jan 2026 20:59:38 +0100
 
diff --git a/debian/patches/CVE-2026-22262_1.patch 
b/debian/patches/CVE-2026-22262_1.patch
new file mode 100644
index 00000000..d2d0ef47
--- /dev/null
+++ b/debian/patches/CVE-2026-22262_1.patch
@@ -0,0 +1,42 @@
+From 32609e6896f9079c175665a94005417cec7637eb Mon Sep 17 00:00:00 2001
+From: Philippe Antoine <[email protected]>
+Date: Mon, 17 Nov 2025 13:27:54 +0100
+Subject: [PATCH 1/2] datasets: explicitly errors on too long string
+
+Also avoids stack allocation
+
+Ticket: 8110
+(cherry picked from commit 0eff24213763c2aa2bb0957901d5dc1e18414dbf)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/32609e6896f9079c175665a94005417cec7637eb.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8110
+Subject: Upstream fix for CVE-2026-22262 part 1
+---
+ src/datasets-string.c | 9 +++++----
+ 1 file changed, 5 insertions(+), 4 deletions(-)
+
+diff --git a/src/datasets-string.c b/src/datasets-string.c
+index 0a8f499ae..524a60ad9 100644
+--- a/src/datasets-string.c
++++ b/src/datasets-string.c
+@@ -49,12 +49,13 @@ int StringAsBase64(const void *s, char *out, size_t 
out_size)
+     const StringType *str = s;
+ 
+     unsigned long len = Base64EncodeBufferSize(str->len);
+-    uint8_t encoded_data[len];
+-    if (Base64Encode((unsigned char *)str->ptr, str->len,
+-        encoded_data, &len) != SC_BASE64_OK)
++    if (len + 2 > out_size) {
++        // linefeed and final zero
++        return 0;
++    }
++    if (Base64Encode((unsigned char *)str->ptr, str->len, (uint8_t *)out, 
&len) != SC_BASE64_OK)
+         return 0;
+ 
+-    strlcpy(out, (const char *)encoded_data, out_size);
+     strlcat(out, "\n", out_size);
+     return strlen(out);
+ }
+-- 
+2.47.3
+
diff --git a/debian/patches/CVE-2026-22262_2.patch 
b/debian/patches/CVE-2026-22262_2.patch
new file mode 100644
index 00000000..c59e27a9
--- /dev/null
+++ b/debian/patches/CVE-2026-22262_2.patch
@@ -0,0 +1,66 @@
+From 27a2180bceaa3477419c78c54fce364398d011f1 Mon Sep 17 00:00:00 2001
+From: Philippe Antoine <[email protected]>
+Date: Tue, 25 Nov 2025 14:43:18 +0100
+Subject: [PATCH 2/2] datasets: allocates on the heap if string base64 is long
+
+Ticket: 8110
+(cherry picked from commit d6bc718e303ecbec5999066b8bc88eeeca743658)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/27a2180bceaa3477419c78c54fce364398d011f1.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8110
+Subject: Upstream fix for CVE-2026-22262 part 2
+---
+ src/datasets-string.c |  4 ++--
+ src/util-thash.c      | 21 ++++++++++++++++++++-
+ 2 files changed, 22 insertions(+), 3 deletions(-)
+
+diff --git a/src/datasets-string.c b/src/datasets-string.c
+index 524a60ad9..53a179a10 100644
+--- a/src/datasets-string.c
++++ b/src/datasets-string.c
+@@ -50,8 +50,8 @@ int StringAsBase64(const void *s, char *out, size_t out_size)
+ 
+     unsigned long len = Base64EncodeBufferSize(str->len);
+     if (len + 2 > out_size) {
+-        // linefeed and final zero
+-        return 0;
++        // linefeed and final zero : signal we need more space
++        return len + 2;
+     }
+     if (Base64Encode((unsigned char *)str->ptr, str->len, (uint8_t *)out, 
&len) != SC_BASE64_OK)
+         return 0;
+diff --git a/src/util-thash.c b/src/util-thash.c
+index 548637916..c6df02cf3 100644
+--- a/src/util-thash.c
++++ b/src/util-thash.c
+@@ -390,7 +390,26 @@ int THashWalk(THashTableContext *ctx, THashFormatFunc 
FormatterFunc, THashOutput
+             char output_string[1024] = "";
+             int size = FormatterFunc(h->data, output_string, 
sizeof(output_string));
+             if (size > 0) {
+-                if (OutputterFunc(output_ctx, (const uint8_t *)output_string, 
size) < 0) {
++                if (size > 1024) {
++                    // we did not provide enough space on the stack, let's 
allocate on the heap
++                    char *out_alloc = SCCalloc(1, size);
++                    if (out_alloc == NULL) {
++                        err = true;
++                        break;
++                    }
++                    size = FormatterFunc(h->data, out_alloc, size);
++                    if (size == 0) {
++                        err = true;
++                        SCFree(out_alloc);
++                        break;
++                    }
++                    if (OutputterFunc(output_ctx, (const uint8_t *)out_alloc, 
size) < 0) {
++                        err = true;
++                        SCFree(out_alloc);
++                        break;
++                    }
++                    SCFree(out_alloc);
++                } else if (OutputterFunc(output_ctx, (const uint8_t 
*)output_string, size) < 0) {
+                     err = true;
+                     break;
+                 }
+-- 
+2.47.3
+
diff --git a/debian/patches/series b/debian/patches/series
index 8e7dfe52..5ce34583 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -18,3 +18,5 @@ CVE-2025-64330.patch
 CVE-2026-22258_1.patch
 CVE-2026-22258_2.patch
 CVE-2026-22258_3.patch
+CVE-2026-22262_1.patch
+CVE-2026-22262_2.patch
diff --git a/debian/changelog b/debian/changelog
index df66aceb..5099a7fc 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -9,6 +9,8 @@ suricata (1:7.0.10-1+deb13u3) UNRELEASED; urgency=high
     Cherry-Picked from:
     * 32609e6896f9079c175665a94005417cec7637eb
     * 27a2180bceaa3477419c78c54fce364398d011f1
+  * Fix CVE-2026-22264 in 7.0.10.
+    Cherry-Picked from 5789a3d3760dbf33d93fc56c27bd9529e5bdc8f2.
 
  -- Andreas Dolp <[email protected]>  Thu, 15 Jan 2026 20:59:38 +0100
 
diff --git a/debian/patches/CVE-2026-22264.patch 
b/debian/patches/CVE-2026-22264.patch
new file mode 100644
index 00000000..0112b6a6
--- /dev/null
+++ b/debian/patches/CVE-2026-22264.patch
@@ -0,0 +1,84 @@
+From 5789a3d3760dbf33d93fc56c27bd9529e5bdc8f2 Mon Sep 17 00:00:00 2001
+From: Shivani Bhardwaj <[email protected]>
+Date: Mon, 5 Jan 2026 19:27:11 +0530
+Subject: [PATCH] detect/alert: check alert queue capacity before expanding
+
+So far, the alert queue was expanded by doubling in size w/o any
+boundary checks in place. This led to situations where doubling
+the alert_queue_capacity meant overflow of the very same value
+stored in det_ctx.
+This led to heap-use-after-free in some conditions where
+det_ctx->alert_queue_capacity overflowed.
+
+Fix this by capping the max of alert_queue_capacity by checking if its
+expansion could result in an overflow.
+
+Security 8190
+
+(cherry picked from commit ac1eb394181530430fb7262969f423a1bf8f209b)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/5789a3d3760dbf33d93fc56c27bd9529e5bdc8f2.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8190
+Subject: Upstream fix for CVE-2026-22264
+---
+ src/detect-engine-alert.c | 35 +++++++++++++++++++++++++----------
+ 1 file changed, 25 insertions(+), 10 deletions(-)
+
+diff --git a/src/detect-engine-alert.c b/src/detect-engine-alert.c
+index f3e49586e..0cce98196 100644
+--- a/src/detect-engine-alert.c
++++ b/src/detect-engine-alert.c
+@@ -238,6 +238,22 @@ void AlertQueueFree(DetectEngineThreadCtx *det_ctx)
+     det_ctx->alert_queue_capacity = 0;
+ }
+ 
++static inline uint16_t AlertQueueExpandDo(DetectEngineThreadCtx *det_ctx, 
uint16_t new_cap)
++{
++    DEBUG_VALIDATE_BUG_ON(det_ctx->alert_queue_capacity >= new_cap);
++
++    void *tmp_queue = SCRealloc(det_ctx->alert_queue, new_cap * 
sizeof(PacketAlert));
++    if (unlikely(tmp_queue == NULL)) {
++        /* queue capacity didn't change */
++        return det_ctx->alert_queue_capacity;
++    }
++    det_ctx->alert_queue = tmp_queue;
++    det_ctx->alert_queue_capacity = new_cap;
++    SCLogDebug("Alert queue size expanded: %u elements, bytes: %" PRIuMAX "",
++            det_ctx->alert_queue_capacity, (uintmax_t)(new_cap * 
sizeof(PacketAlert)));
++    return new_cap;
++}
++
+ /** \internal
+  * \retval the new capacity
+  */
+@@ -247,18 +263,17 @@ static uint16_t AlertQueueExpand(DetectEngineThreadCtx 
*det_ctx)
+     if (unlikely(g_eps_is_alert_queue_fail_mode))
+         return det_ctx->alert_queue_capacity;
+ #endif
+-    uint16_t new_cap = det_ctx->alert_queue_capacity * 2;
+-    void *tmp_queue = SCRealloc(det_ctx->alert_queue, 
(size_t)(sizeof(PacketAlert) * new_cap));
+-    if (unlikely(tmp_queue == NULL)) {
+-        /* queue capacity didn't change */
++    if (det_ctx->alert_queue_capacity == UINT16_MAX) {
+         return det_ctx->alert_queue_capacity;
+     }
+-    det_ctx->alert_queue = tmp_queue;
+-    det_ctx->alert_queue_capacity = new_cap;
+-    SCLogDebug("Alert queue size doubled: %u elements, bytes: %" PRIuMAX "",
+-            det_ctx->alert_queue_capacity,
+-            (uintmax_t)(sizeof(PacketAlert) * det_ctx->alert_queue_capacity));
+-    return new_cap;
++
++    uint16_t new_cap;
++    if (det_ctx->alert_queue_capacity > (UINT16_MAX / 2)) {
++        new_cap = UINT16_MAX;
++    } else {
++        new_cap = det_ctx->alert_queue_capacity * 2;
++    }
++    return AlertQueueExpandDo(det_ctx, new_cap);
+ }
+ 
+ /** \internal
+-- 
+2.47.3
+
diff --git a/debian/patches/series b/debian/patches/series
index 5ce34583..748d1efa 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -20,3 +20,4 @@ CVE-2026-22258_2.patch
 CVE-2026-22258_3.patch
 CVE-2026-22262_1.patch
 CVE-2026-22262_2.patch
+CVE-2026-22264.patch
diff --git a/debian/changelog b/debian/changelog
index 5099a7fc..99a38a4c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -11,6 +11,15 @@ suricata (1:7.0.10-1+deb13u3) UNRELEASED; urgency=high
     * 27a2180bceaa3477419c78c54fce364398d011f1
   * Fix CVE-2026-22264 in 7.0.10.
     Cherry-Picked from 5789a3d3760dbf33d93fc56c27bd9529e5bdc8f2.
+  * Fix CVE-2026-22259 in 7.0.10.
+    Cherry-Picked from:
+    * 63225d5f8ef64cc65164c0bb1800730842d54942
+    * 635af8dc8be09667689be71d781912718ca1aa49
+    * fdd79bdb14488244604729f1d68ca4bc60000dbd
+    * a6d950315d9b6c1e35c10c24d9bb7128d422c21f
+    With this fix, DNP3 has reduced the default maximum number of
+    outstanding transactions from 500 down to 32.
+    Read the update instructions for Suricata 7.0.14 for more details.
 
  -- Andreas Dolp <[email protected]>  Thu, 15 Jan 2026 20:59:38 +0100
 
diff --git a/debian/patches/CVE-2026-22259_1.patch 
b/debian/patches/CVE-2026-22259_1.patch
new file mode 100644
index 00000000..c61f34a5
--- /dev/null
+++ b/debian/patches/CVE-2026-22259_1.patch
@@ -0,0 +1,38 @@
+From 63225d5f8ef64cc65164c0bb1800730842d54942 Mon Sep 17 00:00:00 2001
+From: Jason Ish <[email protected]>
+Date: Tue, 6 Jan 2026 16:15:09 -0600
+Subject: [PATCH 1/4] dnp3: check done state, not complete state for progress
+
+Complete is a flag used to tell if the message was completely parsed,
+as not all messages may be completely parsed if we don't know all
+their objects. However, they are still "done".
+
+In the alstate-progress callback, check the done flag, not the
+complete flag.
+
+Ticket: #8181
+(cherry picked from commit d61eef9a8a0d92921989479de15e5cbfec3251a9)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/63225d5f8ef64cc65164c0bb1800730842d54942.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8181
+Subject: Upstream fix for CVE-2026-22259 part 1
+---
+ src/app-layer-dnp3.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/app-layer-dnp3.c b/src/app-layer-dnp3.c
+index ecae4ae63..1a8b3cb63 100644
+--- a/src/app-layer-dnp3.c
++++ b/src/app-layer-dnp3.c
+@@ -1438,7 +1438,7 @@ static int DNP3GetAlstateProgress(void *tx, uint8_t 
direction)
+         SCReturnInt(1);
+     }
+ 
+-    if (dnp3tx->complete)
++    if (dnp3tx->done)
+         retval = 1;
+ 
+     SCReturnInt(retval);
+-- 
+2.47.3
+
diff --git a/debian/patches/CVE-2026-22259_2.patch 
b/debian/patches/CVE-2026-22259_2.patch
new file mode 100644
index 00000000..4a8c3aaa
--- /dev/null
+++ b/debian/patches/CVE-2026-22259_2.patch
@@ -0,0 +1,135 @@
+From 635af8dc8be09667689be71d781912718ca1aa49 Mon Sep 17 00:00:00 2001
+From: Jason Ish <[email protected]>
+Date: Tue, 6 Jan 2026 11:06:40 -0600
+Subject: [PATCH 2/4] dnp3: reduce flood threshold to 32 and make configurable
+
+Lower the number of unreplied requests from 500 to 32 to consider a
+flood. At the very least this is an anomaly given the DNP3 spec mentions
+that DNP3 should only have one outstanding request at a time, with an
+exception for unsolicited responses, so in practice no more than 2
+should be seen.
+
+Additionally make this value configurable by introducing the max-tx
+parameter.
+
+Ticket: #8181
+(cherry picked from commit a16f087b93be1ff2f2edf47371866ad9b28593c1)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/635af8dc8be09667689be71d781912718ca1aa49.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8181
+Subject: Upstream fix for CVE-2026-22259 part 2
+---
+ doc/userguide/upgrade.rst | 10 ++++++++++
+ src/app-layer-dnp3.c      | 29 +++++++++++++++++++----------
+ suricata.yaml.in          |  1 +
+ 3 files changed, 30 insertions(+), 10 deletions(-)
+
+--- a/doc/userguide/upgrade.rst
++++ b/doc/userguide/upgrade.rst
+@@ -34,6 +34,16 @@
+ this guide. Those features are either not enabled by default or require
+ dedicated new configuration.
+ 
++Upgrading to 7.0.14 (trixie-security 1:7.0.10-1~bpo13u3)
++-------------------
++
++Other Changes
++~~~~~~~~~~~~~
++- ``dnp3`` has reduced the default maximum number of outstanding
++  transactions from 500 down to 32. A ``max-tx`` parameter has been
++  added to the ``dnp3`` parser for users that need a larger number of
++  in-flight transactions.
++
+ Upgrading to 7.0.9
+ ------------------
+ - The AF_PACKET default block size for both TPACKET_V2 and TPACKET_V3
+--- a/src/app-layer-dnp3.c
++++ b/src/app-layer-dnp3.c
+@@ -40,9 +40,6 @@
+ #include "app-layer-dnp3.h"
+ #include "app-layer-dnp3-objects.h"
+ 
+-/* Default number of unreplied requests to be considered a flood. */
+-#define DNP3_DEFAULT_REQ_FLOOD_COUNT 500
+-
+ #define DNP3_DEFAULT_PORT "20000"
+ 
+ /* Expected values for the start bytes. */
+@@ -93,6 +90,14 @@
+ /* Extract the range code from the object qualifier. */
+ #define DNP3_OBJ_RANGE(x)  (x & 0xf)
+ 
++/* Default number of unreplied requests to be considered a flood.
++ *
++ * DNP3 is a request/response SCADA protocol with typically only 1-2
++ * transactions in flight. But set a limit high enough to allow for
++ * some pipelining but reduce the chance of memory exhaustion
++ * attacks. */
++static uint64_t dnp3_max_tx = 32;
++
+ /* Decoder event map. */
+ SCEnumCharMap dnp3_decoder_event_table[] = {
+     {"FLOODED",           DNP3_DECODER_EVENT_FLOODED},
+@@ -514,7 +519,7 @@
+     TAILQ_INSERT_TAIL(&dnp3->tx_list, tx, next);
+ 
+     /* Check for flood state. */
+-    if (dnp3->unreplied > DNP3_DEFAULT_REQ_FLOOD_COUNT) {
++    if (dnp3->unreplied > dnp3_max_tx && !dnp3->flooded) {
+         DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_FLOODED);
+         dnp3->flooded = 1;
+     }
+@@ -1384,7 +1389,7 @@
+         dnp3->unreplied--;
+ 
+         /* Check flood state. */
+-        if (dnp3->flooded && dnp3->unreplied < DNP3_DEFAULT_REQ_FLOOD_COUNT) {
++        if (dnp3->flooded && dnp3->unreplied < dnp3_max_tx) {
+             dnp3->flooded = 0;
+         }
+ 
+@@ -1430,8 +1435,7 @@
+     int retval = 0;
+ 
+     /* If flooded, "ack" old transactions. */
+-    if (dnp3->flooded && (dnp3->transaction_max -
+-            dnp3tx->tx_num >= DNP3_DEFAULT_REQ_FLOOD_COUNT)) {
++    if (dnp3->flooded && (dnp3->transaction_max - dnp3tx->tx_num >= 
dnp3_max_tx)) {
+         SCLogDebug("flooded: returning tx as done.");
+         SCReturnInt(1);
+     }
+@@ -1604,8 +1608,13 @@
+         AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_DNP3,
+             DNP3GetTxData);
+         AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_DNP3, 
DNP3GetStateData);
+-    }
+-    else {
++
++        /* Parse max-tx configuration. */
++        intmax_t value = 0;
++        if (ConfGetInt("app-layer.protocols.dnp3.max-tx", &value)) {
++            dnp3_max_tx = (uint64_t)value;
++        }
++    } else {
+         SCLogConfig("Parser disabled for protocol %s. "
+             "Protocol detection still on.", proto_name);
+     }
+@@ -2252,7 +2261,7 @@
+     FAIL_IF_NOT(tx->done);
+     FAIL_IF_NOT(DNP3GetAlstateProgress(tx, STREAM_TOSERVER));
+ 
+-    for (int i = 0; i < DNP3_DEFAULT_REQ_FLOOD_COUNT - 1; i++) {
++    for (uint64_t i = 0; i < dnp3_max_tx - 1; i++) {
+         SCMutexLock(&flow.m);
+         FAIL_IF(AppLayerParserParse(NULL, alp_tctx, &flow, ALPROTO_DNP3,
+                 STREAM_TOSERVER, request, sizeof(request)));
+--- a/suricata.yaml.in
++++ b/suricata.yaml.in
+@@ -1161,6 +1161,7 @@
+       enabled: no
+       detection-ports:
+         dp: 20000
++      #max-tx: 32
+ 
+     # SCADA EtherNet/IP and CIP protocol support
+     enip:
diff --git a/debian/patches/CVE-2026-22259_3.patch 
b/debian/patches/CVE-2026-22259_3.patch
new file mode 100644
index 00000000..f8b84268
--- /dev/null
+++ b/debian/patches/CVE-2026-22259_3.patch
@@ -0,0 +1,138 @@
+From fdd79bdb14488244604729f1d68ca4bc60000dbd Mon Sep 17 00:00:00 2001
+From: Jason Ish <[email protected]>
+Date: Wed, 7 Jan 2026 09:23:09 -0600
+Subject: [PATCH 3/4] dnp3: set a bound on the number of points per message
+
+16384 is used as the max, but a configuration parameter has been
+provided. The reason for setting an upper bound is that bit flags can
+create a memory amplification as we parse them into individual data
+structures.
+
+Ticket: #8181
+(cherry picked from commit 3a32bb5743c35afb3278a6448f7e9669512dbe92)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/fdd79bdb14488244604729f1d68ca4bc60000dbd.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8181
+Subject: Upstream fix for CVE-2026-22259 part 3
+---
+ doc/userguide/upgrade.rst |  9 +++++----
+ rules/dnp3-events.rules   |  5 +++++
+ src/app-layer-dnp3.c      | 33 ++++++++++++++++++++++++++-------
+ src/app-layer-dnp3.h      |  1 +
+ suricata.yaml.in          |  1 +
+ 5 files changed, 38 insertions(+), 11 deletions(-)
+
+--- a/doc/userguide/upgrade.rst
++++ b/doc/userguide/upgrade.rst
+@@ -39,10 +39,11 @@
+ 
+ Other Changes
+ ~~~~~~~~~~~~~
+-- ``dnp3`` has reduced the default maximum number of outstanding
+-  transactions from 500 down to 32. A ``max-tx`` parameter has been
+-  added to the ``dnp3`` parser for users that need a larger number of
+-  in-flight transactions.
++- ``dnp3`` has reduced the maximum number of open transactions from
++  500 down to 32, and the maximum number of points per message from
++  unbounded to 16384. Configuration options, ``max-tx`` and
++  ``max-points`` have been added for users who may need to change
++  these defaults.
+ 
+ Upgrading to 7.0.9
+ ------------------
+--- a/rules/dnp3-events.rules
++++ b/rules/dnp3-events.rules
+@@ -24,3 +24,8 @@
+ # Unknown object.
+ alert dnp3 any any -> any any (msg:"SURICATA DNP3 Unknown object"; \
+       app-layer-event:dnp3.unknown_object; classtype:protocol-command-decode; 
sid:2270004; rev:2;)
++
++# Too many points in a message.
++alert dnp3 any any -> any any (msg:"SURICATA DNP3 Too many points in 
message"; \
++      app-layer-event:dnp3.too_many_points; \
++      classtype:protocol-command-decode; sid:2270005; rev:1;)
+--- a/src/app-layer-dnp3.c
++++ b/src/app-layer-dnp3.c
+@@ -98,15 +98,19 @@
+  * attacks. */
+ static uint64_t dnp3_max_tx = 32;
+ 
++/* The maximum number of points allowed per message (configurable). */
++static uint64_t max_points = 16384;
++
+ /* Decoder event map. */
+ SCEnumCharMap dnp3_decoder_event_table[] = {
+-    {"FLOODED",           DNP3_DECODER_EVENT_FLOODED},
+-    {"LEN_TOO_SMALL",     DNP3_DECODER_EVENT_LEN_TOO_SMALL},
+-    {"BAD_LINK_CRC",      DNP3_DECODER_EVENT_BAD_LINK_CRC},
+-    {"BAD_TRANSPORT_CRC", DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC},
+-    {"MALFORMED",         DNP3_DECODER_EVENT_MALFORMED},
+-    {"UNKNOWN_OBJECT",    DNP3_DECODER_EVENT_UNKNOWN_OBJECT},
+-    {NULL, -1},
++    { "FLOODED", DNP3_DECODER_EVENT_FLOODED },
++    { "LEN_TOO_SMALL", DNP3_DECODER_EVENT_LEN_TOO_SMALL },
++    { "BAD_LINK_CRC", DNP3_DECODER_EVENT_BAD_LINK_CRC },
++    { "BAD_TRANSPORT_CRC", DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC },
++    { "MALFORMED", DNP3_DECODER_EVENT_MALFORMED },
++    { "UNKNOWN_OBJECT", DNP3_DECODER_EVENT_UNKNOWN_OBJECT },
++    { "TOO_MANY_POINTS", DNP3_DECODER_EVENT_TOO_MANY_POINTS },
++    { NULL, -1 },
+ };
+ 
+ /* Calculate the next transport sequence number. */
+@@ -709,6 +713,7 @@
+     uint32_t len, DNP3ObjectList *objects)
+ {
+     int retval = 0;
++    uint64_t point_count = 0;
+ 
+     if (buf == NULL || len == 0) {
+         return 1;
+@@ -839,6 +844,13 @@
+             goto next;
+         }
+ 
++        /* Check if we've exceeded the maximum number of points per message. 
*/
++        point_count += object->count;
++        if (point_count > max_points) {
++            DNP3SetEventTx(tx, DNP3_DECODER_EVENT_TOO_MANY_POINTS);
++            goto done;
++        }
++
+         int event = DNP3DecodeObject(header->group, header->variation, &buf,
+             &len, object->prefix_code, object->start, object->count,
+             object->points);
+@@ -1614,6 +1626,13 @@
+         if (ConfGetInt("app-layer.protocols.dnp3.max-tx", &value)) {
+             dnp3_max_tx = (uint64_t)value;
+         }
++
++        /* Parse max-points configuration. */
++        if (ConfGetInt("app-layer.protocols.dnp3.max-points", &value)) {
++            if (value > 0) {
++                max_points = (uint64_t)value;
++            }
++        }
+     } else {
+         SCLogConfig("Parser disabled for protocol %s. "
+             "Protocol detection still on.", proto_name);
+--- a/src/app-layer-dnp3.h
++++ b/src/app-layer-dnp3.h
+@@ -109,6 +109,7 @@
+     DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC,
+     DNP3_DECODER_EVENT_MALFORMED,
+     DNP3_DECODER_EVENT_UNKNOWN_OBJECT,
++    DNP3_DECODER_EVENT_TOO_MANY_POINTS,
+ };
+ 
+ /**
+--- a/suricata.yaml.in
++++ b/suricata.yaml.in
+@@ -1162,6 +1162,7 @@
+       detection-ports:
+         dp: 20000
+       #max-tx: 32
++      #max-points: 16384
+ 
+     # SCADA EtherNet/IP and CIP protocol support
+     enip:
diff --git a/debian/patches/CVE-2026-22259_4.patch 
b/debian/patches/CVE-2026-22259_4.patch
new file mode 100644
index 00000000..fcefd630
--- /dev/null
+++ b/debian/patches/CVE-2026-22259_4.patch
@@ -0,0 +1,125 @@
+From a6d950315d9b6c1e35c10c24d9bb7128d422c21f Mon Sep 17 00:00:00 2001
+From: Jason Ish <[email protected]>
+Date: Tue, 6 Jan 2026 17:14:21 -0600
+Subject: [PATCH 4/4] dnp3: bound the maximum number of objects per tx
+
+Default to 2048, but provide a user configuration value.
+
+Ticket: #8181
+(cherry picked from commit 2c95f1ff44e17c3bc8693d5e23e175f2bc90ea10)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/a6d950315d9b6c1e35c10c24d9bb7128d422c21f.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8181
+Subject: Upstream fix for CVE-2026-22259 part 4
+---
+ doc/userguide/upgrade.rst |  9 +++++----
+ rules/dnp3-events.rules   |  5 +++++
+ src/app-layer-dnp3.c      | 18 ++++++++++++++++++
+ src/app-layer-dnp3.h      |  1 +
+ suricata.yaml.in          |  1 +
+ 5 files changed, 30 insertions(+), 4 deletions(-)
+
+--- a/doc/userguide/upgrade.rst
++++ b/doc/userguide/upgrade.rst
+@@ -40,10 +40,11 @@
+ Other Changes
+ ~~~~~~~~~~~~~
+ - ``dnp3`` has reduced the maximum number of open transactions from
+-  500 down to 32, and the maximum number of points per message from
+-  unbounded to 16384. Configuration options, ``max-tx`` and
+-  ``max-points`` have been added for users who may need to change
+-  these defaults.
++  500 down to 32, the maximum number of points per message from
++  unbounded to 16384, and the maximum number of objects per message
++  from unbounded to 2048. Configuration options, ``max-tx``,
++  ``max-points``, and ``max-objects`` have been added for users who
++  may need to change these defaults.
+ 
+ Upgrading to 7.0.9
+ ------------------
+--- a/rules/dnp3-events.rules
++++ b/rules/dnp3-events.rules
+@@ -29,3 +29,8 @@
+ alert dnp3 any any -> any any (msg:"SURICATA DNP3 Too many points in 
message"; \
+       app-layer-event:dnp3.too_many_points; \
+       classtype:protocol-command-decode; sid:2270005; rev:1;)
++
++# Too many objects.
++alert dnp3 any any -> any any (msg:"SURICATA DNP3 Too many objects"; \
++      app-layer-event:dnp3.too_many_objects; \
++      classtype:protocol-command-decode; sid:2270006; rev:1;)
+--- a/src/app-layer-dnp3.c
++++ b/src/app-layer-dnp3.c
+@@ -101,6 +101,9 @@
+ /* The maximum number of points allowed per message (configurable). */
+ static uint64_t max_points = 16384;
+ 
++/* The maximum number of objects allowed per message (configurable). */
++static uint64_t dnp3_max_objects = 2048;
++
+ /* Decoder event map. */
+ SCEnumCharMap dnp3_decoder_event_table[] = {
+     { "FLOODED", DNP3_DECODER_EVENT_FLOODED },
+@@ -110,6 +113,7 @@
+     { "MALFORMED", DNP3_DECODER_EVENT_MALFORMED },
+     { "UNKNOWN_OBJECT", DNP3_DECODER_EVENT_UNKNOWN_OBJECT },
+     { "TOO_MANY_POINTS", DNP3_DECODER_EVENT_TOO_MANY_POINTS },
++    { "TOO_MANY_OBJECTS", DNP3_DECODER_EVENT_TOO_MANY_OBJECTS },
+     { NULL, -1 },
+ };
+ 
+@@ -714,6 +718,7 @@
+ {
+     int retval = 0;
+     uint64_t point_count = 0;
++    uint64_t object_count = 0;
+ 
+     if (buf == NULL || len == 0) {
+         return 1;
+@@ -728,6 +733,12 @@
+         DNP3ObjHeader *header = (DNP3ObjHeader *)buf;
+         offset += sizeof(DNP3ObjHeader);
+ 
++        /* Check if we've exceeded the maximum number of objects. */
++        if (++object_count > dnp3_max_objects) {
++            DNP3SetEventTx(tx, DNP3_DECODER_EVENT_TOO_MANY_OBJECTS);
++            goto done;
++        }
++
+         DNP3Object *object = DNP3ObjectAlloc();
+         if (unlikely(object == NULL)) {
+             goto done;
+@@ -1633,6 +1644,13 @@
+                 max_points = (uint64_t)value;
+             }
+         }
++
++        /* Parse max-objects configuration. */
++        if (ConfGetInt("app-layer.protocols.dnp3.max-objects", &value)) {
++            if (value > 0) {
++                dnp3_max_objects = (uint64_t)value;
++            }
++        }
+     } else {
+         SCLogConfig("Parser disabled for protocol %s. "
+             "Protocol detection still on.", proto_name);
+--- a/src/app-layer-dnp3.h
++++ b/src/app-layer-dnp3.h
+@@ -110,6 +110,7 @@
+     DNP3_DECODER_EVENT_MALFORMED,
+     DNP3_DECODER_EVENT_UNKNOWN_OBJECT,
+     DNP3_DECODER_EVENT_TOO_MANY_POINTS,
++    DNP3_DECODER_EVENT_TOO_MANY_OBJECTS,
+ };
+ 
+ /**
+--- a/suricata.yaml.in
++++ b/suricata.yaml.in
+@@ -1163,6 +1163,7 @@
+         dp: 20000
+       #max-tx: 32
+       #max-points: 16384
++      #max-objects: 2048
+ 
+     # SCADA EtherNet/IP and CIP protocol support
+     enip:
diff --git a/debian/patches/series b/debian/patches/series
index 748d1efa..dcdc665e 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -21,3 +21,7 @@ CVE-2026-22258_3.patch
 CVE-2026-22262_1.patch
 CVE-2026-22262_2.patch
 CVE-2026-22264.patch
+CVE-2026-22259_1.patch
+CVE-2026-22259_2.patch
+CVE-2026-22259_3.patch
+CVE-2026-22259_4.patch
diff --git a/debian/changelog b/debian/changelog
index 99a38a4c..41692121 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -20,6 +20,10 @@ suricata (1:7.0.10-1+deb13u3) UNRELEASED; urgency=high
     With this fix, DNP3 has reduced the default maximum number of
     outstanding transactions from 500 down to 32.
     Read the update instructions for Suricata 7.0.14 for more details.
+  * Fix CVE-2026-22261 in 7.0.10.
+    Cherry-Picked from:
+    * 44d0c81f537f230e9215c769453fb4d7214217a1
+    * 7e704a3f50690b5f5d5cc573147ef41449fe37ac
 
  -- Andreas Dolp <[email protected]>  Thu, 15 Jan 2026 20:59:38 +0100
 
diff --git a/debian/patches/CVE-2026-22261_1.patch 
b/debian/patches/CVE-2026-22261_1.patch
new file mode 100644
index 00000000..4d9cbb03
--- /dev/null
+++ b/debian/patches/CVE-2026-22261_1.patch
@@ -0,0 +1,68 @@
+From 44d0c81f537f230e9215c769453fb4d7214217a1 Mon Sep 17 00:00:00 2001
+From: Philippe Antoine <[email protected]>
+Date: Tue, 9 Dec 2025 09:21:58 +0100
+Subject: [PATCH 1/2] output: optimize loop for finding alert http xff
+
+Ticket: 8156
+
+In case of non-tx alerts, we try to loop over all the txs to find
+the xff header. Do not start from tx_id 0, but from min_id
+as AppLayerParserTransactionsCleanup to skip txs that were freed
+
+(cherry picked from commit 3b1a6c1711b8f7d0bde4cb05f15cf50c751eda60)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/44d0c81f537f230e9215c769453fb4d7214217a1.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8156
+Subject: Upstream fix for CVE-2026-22261 part 1
+---
+ src/app-layer-htp-xff.c | 2 +-
+ src/app-layer-parser.c  | 7 +++++++
+ src/app-layer-parser.h  | 1 +
+ 3 files changed, 9 insertions(+), 1 deletion(-)
+
+diff --git a/src/app-layer-htp-xff.c b/src/app-layer-htp-xff.c
+index c145e5818..6eae5b1b3 100644
+--- a/src/app-layer-htp-xff.c
++++ b/src/app-layer-htp-xff.c
+@@ -180,7 +180,7 @@ int HttpXFFGetIPFromTx(const Flow *f, uint64_t tx_id, 
HttpXFFCfg *xff_cfg,
+ int HttpXFFGetIP(const Flow *f, HttpXFFCfg *xff_cfg, char *dstbuf, int 
dstbuflen)
+ {
+     HtpState *htp_state = NULL;
+-    uint64_t tx_id = 0;
++    uint64_t tx_id = AppLayerParserGetMinId(f->alparser);
+     uint64_t total_txs = 0;
+ 
+     htp_state = (HtpState *)FlowGetAppState(f);
+diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c
+index 627bf929a..2b8a59a14 100644
+--- a/src/app-layer-parser.c
++++ b/src/app-layer-parser.c
+@@ -721,6 +721,13 @@ uint64_t 
AppLayerParserGetTransactionLogId(AppLayerParserState *pstate)
+     SCReturnCT((pstate == NULL) ? 0 : pstate->log_id, "uint64_t");
+ }
+ 
++uint64_t AppLayerParserGetMinId(AppLayerParserState *pstate)
++{
++    SCEnter();
++
++    SCReturnCT((pstate == NULL) ? 0 : pstate->min_id, "uint64_t");
++}
++
+ void AppLayerParserSetTransactionLogId(AppLayerParserState *pstate, uint64_t 
tx_id)
+ {
+     SCEnter();
+diff --git a/src/app-layer-parser.h b/src/app-layer-parser.h
+index 77e8c813f..dad0b613c 100644
+--- a/src/app-layer-parser.h
++++ b/src/app-layer-parser.h
+@@ -230,6 +230,7 @@ void 
AppLayerParserDestroyProtocolParserLocalStorage(uint8_t ipproto, AppProto a
+ 
+ 
+ uint64_t AppLayerParserGetTransactionLogId(AppLayerParserState *pstate);
++uint64_t AppLayerParserGetMinId(AppLayerParserState *pstate);
+ void AppLayerParserSetTransactionLogId(AppLayerParserState *pstate, uint64_t 
tx_id);
+ 
+ uint64_t AppLayerParserGetTransactionInspectId(AppLayerParserState *pstate, 
uint8_t direction);
+-- 
+2.47.3
+
diff --git a/debian/patches/CVE-2026-22261_2.patch 
b/debian/patches/CVE-2026-22261_2.patch
new file mode 100644
index 00000000..cb04f757
--- /dev/null
+++ b/debian/patches/CVE-2026-22261_2.patch
@@ -0,0 +1,128 @@
+From 7e704a3f50690b5f5d5cc573147ef41449fe37ac Mon Sep 17 00:00:00 2001
+From: Philippe Antoine <[email protected]>
+Date: Tue, 9 Dec 2025 09:38:31 +0100
+Subject: [PATCH 2/2] output: use tx iterator for finding alert http xff
+
+Ticket: 8156
+
+Allows better performance.
+
+(cherry picked from commit ab2e128176744ead5130707bb53fa59038e19634)
+
+Origin: upstream, 
https://github.com/OISF/suricata/commit/7e704a3f50690b5f5d5cc573147ef41449fe37ac.patch
+Bug: https://redmine.openinfosecfoundation.org/issues/8156
+Subject: Upstream fix for CVE-2026-22261 part 2
+---
+ src/app-layer-htp-xff.c | 77 +++++++++++++++++++++++++----------------
+ 1 file changed, 47 insertions(+), 30 deletions(-)
+
+diff --git a/src/app-layer-htp-xff.c b/src/app-layer-htp-xff.c
+index 6eae5b1b3..2e5c25cdb 100644
+--- a/src/app-layer-htp-xff.c
++++ b/src/app-layer-htp-xff.c
+@@ -107,38 +107,12 @@ static int ParseXFFString(char *input, char *output, int 
output_size)
+     return 0;
+ }
+ 
+-/**
+- * \brief Function to return XFF IP if any in the selected transaction. The
+- * caller needs to lock the flow.
+- * \retval 1 if the IP has been found and returned in dstbuf
+- * \retval 0 if the IP has not being found or error
+- */
+-int HttpXFFGetIPFromTx(const Flow *f, uint64_t tx_id, HttpXFFCfg *xff_cfg,
+-        char *dstbuf, int dstbuflen)
++static int HttpXFFGetIPFromTxAux(
++        const Flow *f, htp_tx_t *tx, HttpXFFCfg *xff_cfg, char *dstbuf, int 
dstbuflen)
+ {
+     uint8_t xff_chain[XFF_CHAIN_MAXLEN];
+-    HtpState *htp_state = NULL;
+-    htp_tx_t *tx = NULL;
+-    uint64_t total_txs = 0;
+     uint8_t *p_xff = NULL;
+ 
+-    htp_state = (HtpState *)FlowGetAppState(f);
+-
+-    if (htp_state == NULL) {
+-        SCLogDebug("no http state, XFF IP cannot be retrieved");
+-        return 0;
+-    }
+-
+-    total_txs = AppLayerParserGetTxCnt(f, htp_state);
+-    if (tx_id >= total_txs)
+-        return 0;
+-
+-    tx = AppLayerParserGetTx(f->proto, ALPROTO_HTTP1, htp_state, tx_id);
+-    if (tx == NULL) {
+-        SCLogDebug("tx is NULL, XFF cannot be retrieved");
+-        return 0;
+-    }
+-
+     htp_header_t *h_xff = NULL;
+     if (tx->request_headers != NULL) {
+         h_xff = htp_table_get_c(tx->request_headers, xff_cfg->header);
+@@ -172,6 +146,38 @@ int HttpXFFGetIPFromTx(const Flow *f, uint64_t tx_id, 
HttpXFFCfg *xff_cfg,
+     return 0;
+ }
+ 
++/**
++ * \brief Function to return XFF IP if any in the selected transaction. The
++ * caller needs to lock the flow.
++ * \retval 1 if the IP has been found and returned in dstbuf
++ * \retval 0 if the IP has not being found or error
++ */
++int HttpXFFGetIPFromTx(
++        const Flow *f, uint64_t tx_id, HttpXFFCfg *xff_cfg, char *dstbuf, int 
dstbuflen)
++{
++    HtpState *htp_state = NULL;
++    uint64_t total_txs = 0;
++    htp_tx_t *tx = NULL;
++
++    htp_state = (HtpState *)FlowGetAppState(f);
++
++    if (htp_state == NULL) {
++        SCLogDebug("no http state, XFF IP cannot be retrieved");
++        return 0;
++    }
++
++    total_txs = AppLayerParserGetTxCnt(f, htp_state);
++    if (tx_id >= total_txs)
++        return 0;
++
++    tx = AppLayerParserGetTx(f->proto, ALPROTO_HTTP1, htp_state, tx_id);
++    if (tx == NULL) {
++        SCLogDebug("tx is NULL, XFF cannot be retrieved");
++        return 0;
++    }
++    return HttpXFFGetIPFromTxAux(f, tx, xff_cfg, dstbuf, dstbuflen);
++}
++
+ /**
+  *  \brief Function to return XFF IP if any. The caller needs to lock the 
flow.
+  *  \retval 1 if the IP has been found and returned in dstbuf
+@@ -190,9 +196,20 @@ int HttpXFFGetIP(const Flow *f, HttpXFFCfg *xff_cfg, char 
*dstbuf, int dstbuflen
+     }
+ 
+     total_txs = AppLayerParserGetTxCnt(f, htp_state);
+-    for (; tx_id < total_txs; tx_id++) {
+-        if (HttpXFFGetIPFromTx(f, tx_id, xff_cfg, dstbuf, dstbuflen) == 1)
++    AppLayerGetTxIteratorFunc IterFunc = AppLayerGetTxIterator(f->proto, 
f->alproto);
++    AppLayerGetTxIterState state;
++    memset(&state, 0, sizeof(state));
++
++    while (1) {
++        AppLayerGetTxIterTuple ires =
++                IterFunc(f->proto, f->alproto, f->alstate, tx_id, total_txs, 
&state);
++        if (ires.tx_ptr == NULL)
++            break;
++
++        if (HttpXFFGetIPFromTxAux(f, ires.tx_ptr, xff_cfg, dstbuf, dstbuflen) 
== 1)
+             return 1;
++
++        tx_id = ires.tx_id + 1;
+     }
+ 
+ end:
+-- 
+2.47.3
+
diff --git a/debian/patches/series b/debian/patches/series
index dcdc665e..54f84b03 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -25,3 +25,5 @@ CVE-2026-22259_1.patch
 CVE-2026-22259_2.patch
 CVE-2026-22259_3.patch
 CVE-2026-22259_4.patch
+CVE-2026-22261_1.patch
+CVE-2026-22261_2.patch

Reply via email to