Hello,

I've prepared ntfs-3g 1:2022.10.3-1+deb12u4 for bookworm-security, and it's 
ready for review. The debdiff against 1:2022.10.3-1+deb12u3 is attached.

DSA-6389-1 shipped the July fixes in ntfs-3g 1:2022.10.3-5+deb13u2, and 
DSA-6557-1 shipped NTFS-3G-SA_2026-06-1 in ntfs-3g 1:2022.10.3-5+deb13u3. Those 
follow upstream 2026.7.7 
(https://github.com/tuxera/ntfs-3g/releases/tag/2026.7.7), upstream 2026.9.18 
(https://github.com/tuxera/ntfs-3g/releases/tag/2026.9.18), and the 
multi-sector-transfer revert in upstream 2026.9.28 
(https://github.com/tuxera/ntfs-3g/releases/tag/2026.9.28) after 
https://github.com/tuxera/ntfs-3g/issues/217 and 
https://github.com/tuxera/ntfs-3g/issues/218.

Upstream 2026.7.7 fixes:

- CVE-2026-42616, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-p6mp-gp2j-7358
- CVE-2026-42617, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-jv65-qqf7-f692
- CVE-2026-42618, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-6whp-3f63-97qw
- CVE-2026-46569, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-xxv8-9r24-hcj9
- CVE-2026-46570, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-m6qq-pv5j-2wxc
- CVE-2026-46571, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-52gr-j4pv-9pjh
- CVE-2026-46572, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-wx5r-gc7w-9hhc
- CVE-2026-56135, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-c9qg-fh4v-mq8r
- CVE-2026-56136, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-r66g-c39x-cw95

Upstream 2026.9.18 fixes:

- GHSA-r6xj-6488-p8mv, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-r6xj-6488-p8mv
- GHSA-mc3c-983p-wqm8, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-mc3c-983p-wqm8
- GHSA-wf3w-fjjg-x4w3, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-wf3w-fjjg-x4w3
- GHSA-2c97-47cr-9xr8, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-2c97-47cr-9xr8
- GHSA-xrvx-6jrp-4q3x, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-xrvx-6jrp-4q3x
- GHSA-jcjj-9262-6j6p, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-jcjj-9262-6j6p
- GHSA-x98j-3g35-f59x, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-x98j-3g35-f59x
- GHSA-pc48-m7cx-qf72, 
https://github.com/tuxera/ntfs-3g/security/advisories/GHSA-pc48-m7cx-qf72

The mapping-pair fix in 2026.9.18 is paired here with the multi-sector-transfer 
revert from 2026.9.28, so creating files and directories keeps working.

For testing, I built deb12u3 and deb12u4 with AddressSanitizer in a bookworm 
schroot and ran the test cases from every upstream advisory against both.

On deb12u3, ASan flags errors while walking compressed blocks, indexes, 
reparse-point names, attribute lists, extended attributes, the log-file restart 
area, and ACLs. For example, ntfscat writes past its buffer when a directory's 
index block is 65536 bytes, the mapping-pair decoder starts the first run at 
VCN 65536 instead of 0, and attaching every extent of an attribute hangs until 
the test times out.

deb12u4 runs the same cases clean under ASan. The first mapping-pair run stays 
at VCN 0, and the attribute-extent walk returns an I/O error instead of 
hanging. Both versions reject a zero-size ACE, so that case isn't a regression.

With the packaged binaries, I created a volume, then wrote, renamed, and read 
back a file through ntfs-3g. I also ran ntfscat, ntfsls, ntfsinfo, and ntfsfix, 
mounted the volume read-only, and ran ntfs-3g.probe and lowntfs-3g. ntfsclone 
works before ntfsfix. After ntfsfix it needs --force, because ntfsfix leaves 
the volume marked dirty.

Here is the Debusine work request: 
https://debusine.debian.net/debian/developers/work-request/1575936/

Feedback is welcome. If nothing showstopping comes up, I'll upload this and 
issue the DLA on Thursday morning (November 5th, 2026).

Best regards,
Simon Quigley
[email protected]
diff -Nru ntfs-3g-2022.10.3/debian/changelog ntfs-3g-2022.10.3/debian/changelog
--- ntfs-3g-2022.10.3/debian/changelog  2026-04-16 16:28:39.000000000 -0500
+++ ntfs-3g-2022.10.3/debian/changelog  2026-11-01 16:13:25.000000000 -0600
@@ -1,3 +1,29 @@
+ntfs-3g (1:2022.10.3-1+deb12u4) bookworm-security; urgency=high
+
+  * Non-maintainer upload by the LTS Team.
+  * CVE-2026-42616: heap buffer overflow in cat() in ntfscat.
+  * CVE-2026-42617: heap buffer overflow in ntfs_ir_truncate().
+  * CVE-2026-42618: heap buffer overflow in ntfs_decompress().
+  * CVE-2026-46569: heap buffer overflow in ntfs_ib_copy_tail().
+  * CVE-2026-46570: heap buffer overflow in ntfs_index_walk_down().
+  * CVE-2026-46571: out-of-bounds read in ntfs_fix_file_name().
+  * CVE-2026-46572: heap buffer overflow in ntfs_ib_cut_tail().
+  * CVE-2026-56135: heap buffer overflow in ACL handling.
+  * CVE-2026-56136: out-of-bounds read in ntfs_ir_lookup().
+  * GHSA-r6xj-6488-p8mv: heap out of bounds read/write in ntfs_ie_add_vcn().
+  * GHSA-mc3c-983p-wqm8: heap corruption in ntfs_mapping_pairs_decompress_i().
+  * GHSA-wf3w-fjjg-x4w3: heap buffer overflow in ntfs_external_attr_find().
+  * GHSA-2c97-47cr-9xr8: heap buffer overflow in ntfs_ea_check_wsldev().
+  * GHSA-xrvx-6jrp-4q3x: heap buffer overflow in ntfs_check_restart_area().
+  * GHSA-jcjj-9262-6j6p: denial of service in ntfs_inode_attach_all_extents().
+  * GHSA-x98j-3g35-f59x: heap buffer overflow in ntfs_same_sid().
+  * GHSA-pc48-m7cx-qf72: heap buffer overflow in ntfs_acl_owner().
+  * Revert the multi-sector transfer hard-fail in attrib.c (9398030d).
+    It came in with GHSA-mc3c-983p-wqm8 and broke creating files and
+    directories (upstream issues #217 and #218).
+
+ -- Simon Quigley <[email protected]>  Sun, 01 Nov 2026 16:13:25 -0600
+
 ntfs-3g (1:2022.10.3-1+deb12u3) bookworm-security; urgency=high
 
   * Non-maintainer upload by the Security Team.
diff -Nru ntfs-3g-2022.10.3/debian/patches/CVE-2026-42616.patch 
ntfs-3g-2022.10.3/debian/patches/CVE-2026-42616.patch
--- ntfs-3g-2022.10.3/debian/patches/CVE-2026-42616.patch       1969-12-31 
18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/CVE-2026-42616.patch       2026-11-01 
16:08:25.000000000 -0600
@@ -0,0 +1,67 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/dc1b682e99b6c055b0281ca543a8420b698420d1
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From dc1b682e99b6c055b0281ca543a8420b698420d1 Mon Sep 17 00:00:00 2001
+From: Erik Larsson <[email protected]>
+Date: Fri, 24 Apr 2026 12:18:24 +0300
+Subject: [PATCH] ntfscat.c: Fix reading outside the buffer when block size >
+ buffer size.
+
+The block size may be read from the index root's 'index_block_size'
+member, and isn't guaranteed to be >= 4096 (as far as I can tell it
+could be larger). This would for MST protected records lead to storing
+data past the end of the allocated buffer.
+
+Fixed by allocating the necessary amount of buffer space for the case
+when the block size exceeds 4096.
+
+Thanks to Alexandro Calo' from Nozomi Networks Labs for reporting this
+issue.
+
+diff --git a/ntfsprogs/ntfscat.c b/ntfsprogs/ntfscat.c
+index b8af25033..0f711815a 100644
+--- a/ntfsprogs/ntfscat.c
++++ b/ntfsprogs/ntfscat.c
+@@ -334,22 +334,17 @@ static int index_get_size(ntfs_inode *inode)
+ static int cat(ntfs_volume *vol, ntfs_inode *inode, ATTR_TYPES type,
+               ntfschar *name, int namelen)
+ {
+-      const int bufsize = 4096;
+       char *buffer;
+       ntfs_attr *attr;
+       s64 bytes_read, written;
+       s64 offset;
+       u32 block_size;
+-
+-      buffer = malloc(bufsize);
+-      if (!buffer)
+-              return 1;
++      u32 bufsize = 4096;
+ 
+       attr = ntfs_attr_open(inode, type, name, namelen);
+       if (!attr) {
+               ntfs_log_error("Cannot find attribute type 0x%x.\n",
+                               le32_to_cpu(type));
+-              free(buffer);
+               return 1;
+       }
+ 
+@@ -360,6 +355,15 @@ static int cat(ntfs_volume *vol, ntfs_inode *inode, 
ATTR_TYPES type,
+       else
+               block_size = 0;
+ 
++      if (bufsize < block_size) {
++              bufsize = block_size;
++      }
++
++      buffer = malloc(bufsize);
++      if (!buffer) {
++              return 1;
++      }
++
+       offset = 0;
+       for (;;) {
+               if (!opts.raw && block_size > 0) {
diff -Nru ntfs-3g-2022.10.3/debian/patches/CVE-2026-42617.patch 
ntfs-3g-2022.10.3/debian/patches/CVE-2026-42617.patch
--- ntfs-3g-2022.10.3/debian/patches/CVE-2026-42617.patch       1969-12-31 
18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/CVE-2026-42617.patch       2026-11-01 
16:08:25.000000000 -0600
@@ -0,0 +1,63 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/2ead8a3ccf940c0219ffba587fab54e21462b3a5
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From 2ead8a3ccf940c0219ffba587fab54e21462b3a5 Mon Sep 17 00:00:00 2001
+From: Erik Larsson <[email protected]>
+Date: Fri, 24 Apr 2026 13:00:30 +0300
+Subject: [PATCH] index.c: Validate the size of data copied from root to index
+ block.
+
+If the last entry extends beyond the index block size, we could copy
+more bytes than allocated into the index block, overflowing its
+allocation. Fixed by adding a bounds check prior to the memcpy, emitting
+an error message and returning NULL with errno == EIO as this is an
+inconsistency.
+
+Thanks to Alexandro Calo' from Nozomi Networks Labs for reporting this
+issue.
+
+
+diff --git a/libntfs-3g/index.c b/libntfs-3g/index.c
+--- a/libntfs-3g/index.c
++++ b/libntfs-3g/index.c
+@@ -1091,14 +1091,16 @@
+ 
+ static INDEX_BLOCK *ntfs_ir_to_ib(INDEX_ROOT *ir, VCN ib_vcn)
+ {
++      u32 ib_size;
+       INDEX_BLOCK *ib;
+       INDEX_ENTRY *ie_last;
+       char *ies_start, *ies_end;
+       int i;
+       
+       ntfs_log_trace("Entering\n");
+-      
+-      ib = ntfs_ib_alloc(ib_vcn, le32_to_cpu(ir->index_block_size), 
LEAF_NODE);
++
++      ib_size = le32_to_cpu(ir->index_block_size);
++      ib = ntfs_ib_alloc(ib_vcn, ib_size, LEAF_NODE);
+       if (!ib)
+               return NULL;
+       
+@@ -1110,6 +1112,18 @@
+        * as well, which can never have any data.
+        */
+       i = (char *)ie_last - ies_start + le16_to_cpu(ie_last->length);
++
++      if (offsetof(INDEX_BLOCK, index) + le32_to_cpu(ib->index.entries_offset)
++                      + i > ib_size)
++      {
++              ntfs_log_error("Last entry in index root overflows the index "
++                              "block size: %d (index block size: %lu)\n",
++                              i, (unsigned long)ib_size);
++              free(ib);
++              errno = EIO;
++              return NULL;
++      }
++
+       memcpy(ntfs_ie_get_first(&ib->index), ies_start, i);
+       
+       ib->index.ih_flags = ir->index.ih_flags;
diff -Nru ntfs-3g-2022.10.3/debian/patches/CVE-2026-42618-6aa66d98.patch 
ntfs-3g-2022.10.3/debian/patches/CVE-2026-42618-6aa66d98.patch
--- ntfs-3g-2022.10.3/debian/patches/CVE-2026-42618-6aa66d98.patch      
1969-12-31 18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/CVE-2026-42618-6aa66d98.patch      
2026-11-01 16:08:25.000000000 -0600
@@ -0,0 +1,400 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/6aa66d98980ac1d966d7977c7b079f4fe380eb42
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From 6aa66d98980ac1d966d7977c7b079f4fe380eb42 Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Thu, 30 Apr 2026 19:33:52 +0300
+Subject: [PATCH] compress.c: Harden LZNT1 sub-block decompression bounds
+ checks.
+
+ntfs_decompress() could continue decoding tokens after the current
+4 KiB destination sub-block was already full.  If the next token was a
+symbol token, the decoder wrote one byte at dest_sb_end, outside the
+valid destination range.
+
+Finish the current compressed sub-block when either its compressed input
+is exhausted or its output sub-block is full.  If the output fills
+first, skip the remaining bytes in that compressed sub-block so they are
+not interpreted as more tags or tokens.
+
+While here, read the sub-block header only after verifying that two
+input bytes remain, reuse the decoded header value, and reject truncated
+phrase tokens before reading their two-byte compressed word.
+
+Also fix the function comment to match the userspace return convention:
+return 0 on success, or return -1 with errno set to EOVERFLOW on
+malformed compressed input.
+
+Thanks to Alexandro Calo' from Nozomi Networks Labs for reporting this
+issue.
+
+diff --git a/libntfs-3g/compress.c b/libntfs-3g/compress.c
+--- a/libntfs-3g/compress.c
++++ b/libntfs-3g/compress.c
+@@ -459,7 +459,8 @@
+  * @cb_start is a pointer to the compression block which needs decompressing
+  * and @cb_size is the size of @cb_start in bytes (8-64kiB).
+  *
+- * Return 0 if success or -EOVERFLOW on error in the compressed stream.
++ * Return 0 on success.  On error in the compressed stream, set errno to
++ * EOVERFLOW and return -1.
+  */
+ static int ntfs_decompress(u8 *dest, const u32 dest_size,
+               u8 *const cb_start, const u32 cb_size)
+@@ -479,153 +480,211 @@
+       /* Variables for tag and token parsing. */
+       u8 tag;                 /* Current tag. */
+       int token;              /* Loop counter for the eight tokens in tag. */
++      u16 hdr;                /* Current sub-block header. */
+ 
+       ntfs_log_trace("Entering, cb_size = 0x%x.\n", (unsigned)cb_size);
+ do_next_sb:
+-      ntfs_log_debug("Beginning sub-block at offset = %d in the cb.\n",
+-                      (int)(cb - cb_start));
++      {
++              ntfs_log_debug("Beginning sub-block at offset = %d in the "
++                              "cb.\n",
++                              (int)(cb - cb_start));
++              /*
++               * Have we reached the end of the compression block or the end
++               * of the decompressed data?
++               *
++               * Check dest == dest_end before reading the next two-byte sub-
++               * block header.  The compressed pointer can legally be near the
++               * end of the buffer when the requested output has already been
++               * produced.
++               */
++              if (cb == cb_end || dest == dest_end)
++                      goto complete;
++              /* A sub-block header is two bytes. */
++              if ((size_t)(cb_end - cb) < 2)
++                      goto return_overflow;
++              hdr = le16_to_cpup((le16*)cb);
++              if (!hdr)
++                      goto complete;
++              /* Setup offset for the current sub-block destination. */
++              dest_sb_start = dest;
++              dest_sb_end = dest + NTFS_SB_SIZE;
++              /* Check that we are still within allowed boundaries. */
++              if (dest_sb_end > dest_end)
++                      goto return_overflow;
++              /* Does the minimum size of a compressed sb overflow valid
++               * range? */
++              if ((size_t)(cb_end - cb) < 6)
++                      goto return_overflow;
++              /* Setup the current sub-block source pointers and validate
++               * range. */
++              cb_sb_start = cb;
++              cb_sb_end = cb_sb_start + (hdr & NTFS_SB_SIZE_MASK) + 3;
++              if (cb_sb_end > cb_end)
++                      goto return_overflow;
++              /* Now, we are ready to process the current sub-block (sb). */
++              if (!(hdr & NTFS_SB_IS_COMPRESSED)) {
++                      ntfs_log_debug("Found uncompressed sub-block.\n");
++                      /* This sb is not compressed, just copy it into
++                       * destination. */
++                      /* Advance source position to first data byte. */
++                      cb += 2;
++                      /* An uncompressed sb must be full size. */
++                      if (cb_sb_end - cb != NTFS_SB_SIZE)
++                              goto return_overflow;
++                      /* Copy the block and advance the source position. */
++                      memcpy(dest, cb, NTFS_SB_SIZE);
++                      cb += NTFS_SB_SIZE;
++                      /* Advance destination position to next sub-block. */
++                      dest += NTFS_SB_SIZE;
++                      goto do_next_sb;
++              }
++              ntfs_log_debug("Found compressed sub-block.\n");
++              /* This sb is compressed, decompress it into destination. */
++              /* Forward to the first tag in the sub-block. */
++              cb += 2;
++do_next_tag:
++              {
++                      /* Check we are still in range. */
++                      if (cb > cb_sb_end || dest > dest_sb_end)
++                              goto return_overflow;
++                      /*
++                       * Finish this compressed sub-block when either its
++                       * compressed input is exhausted or its 4 KiB output
++                       * slot is full.  If output becomes full first, skip the
++                       * remaining compressed bytes in this sub-block instead
++                       * of interpreting them as more tags/tokens.
++                       */
++                      if (cb == cb_sb_end || dest == dest_sb_end)
++                              goto finish_compressed_sb;
++                      /* Get the next tag and advance to first token. */
++                      tag = *cb++;
++                      /* Parse the eight tokens described by the tag. */
++                      for (token = 0; token < 8; token++, tag >>= 1) {
++                              u16 lg, pt, length, max_non_overlap;
++                              register u16 i;
++                              u8 *dest_back_addr;
++
++                              /* Check if we are done / still in range. */
++                              if (cb > cb_sb_end || dest > dest_sb_end)
++                                      goto return_overflow;
++                              if (cb == cb_sb_end || dest == dest_sb_end)
++                                      goto finish_compressed_sb;
++                              /* Determine token type and parse
++                               * appropriately. */
++                              if ((tag & NTFS_TOKEN_MASK) ==
++                                              NTFS_SYMBOL_TOKEN)
++                              {
++                                      /*
++                                       * We have a symbol token, copy the
++                                       * symbol across, and advance the source
++                                       * and destination positions.
++                                       */
++                                      *dest++ = *cb++;
++                                      /* Continue with the next token. */
++                                      continue;
++                              }
++                              /*
++                               * We have a phrase token. Make sure it is not
++                               * the first token in the sb as this is illegal
++                               * and would confuse the code below.
++                               */
++                              if (dest == dest_sb_start)
++                                      goto return_overflow;
++                              /*
++                               * A phrase token is a two-byte compressed word.
++                               * The generic token check above only proves
++                               * that at least one byte remains, which is
++                               * enough for a symbol token but not for a
++                               * phrase token.
++                               */
++                              if ((size_t)(cb_sb_end - cb) < 2)
++                                      goto return_overflow;
++                              /*
++                               * Determine the number of bytes to go back (p)
++                               * and the number of bytes to copy (l). We use
++                               * an optimized algorithm in which we first
++                               * calculate log2(current destination position
++                               * in sb), which allows determination of l and p
++                               * in O(1) rather than O(n). We just need an
++                               * arch-optimized log2() function now.
++                               */
++                              lg = 0;
++                              for (i = dest - dest_sb_start - 1; i >= 0x10;
++                                              i >>= 1)
++                              {
++                                      lg++;
++                              }
++                              /* Get the phrase token into i. */
++                              pt = le16_to_cpup((le16*)cb);
++                              /*
++                               * Calculate starting position of the byte
++                               * sequence in the destination using the fact
++                               * that p = (pt >> (12 - lg)) + 1 and make sure
++                               * we don't go too far back.
++                               */
++                              dest_back_addr = dest - (pt >> (12 - lg)) - 1;
++                              if (dest_back_addr < dest_sb_start)
++                                      goto return_overflow;
++                              /* Now calculate the length of the byte
++                               * sequence. */
++                              length = (pt & (0xfff >> lg)) + 3;
++                              /* Verify destination is in range. */
++                              if (dest + length > dest_sb_end)
++                                      goto return_overflow;
++                              /* The number of non-overlapping bytes. */
++                              max_non_overlap = dest - dest_back_addr;
++                              if (length <= max_non_overlap) {
++                                      /* The byte sequence doesn't overlap,
++                                       * just copy it. */
++                                      memcpy(dest, dest_back_addr, length);
++                                      /* Advance destination pointer. */
++                                      dest += length;
++                              } else {
++                                      /*
++                                       * The byte sequence does overlap, copy
++                                       * non-overlapping part and then do a
++                                       * slow byte by byte copy for the
++                                       * overlapping part. Also, advance the
++                                       * destination pointer.
++                                       */
++                                      memcpy(dest, dest_back_addr,
++                                              max_non_overlap);
++                                      dest += max_non_overlap;
++                                      dest_back_addr += max_non_overlap;
++                                      length -= max_non_overlap;
++                                      while (length--)
++                                              *dest++ = *dest_back_addr++;
++                              }
++                              /* Advance source position and continue with the
++                               * next token. */
++                              cb += 2;
++                      }
++                      /* No tokens left in the current tag. Continue with the
++                       * next tag. */
++                      goto do_next_tag;
++              }
++      }
++finish_compressed_sb:
++      /* Check if the decompressed sub-block was not full-length. */
++      if (dest < dest_sb_end) {
++              size_t nr_bytes = dest_sb_end - dest;
++
++              ntfs_log_debug("Filling incomplete sub-block with zeroes.\n");
++              /* Zero remainder and update destination position. */
++              memset(dest, 0, nr_bytes);
++              dest += nr_bytes;
++      }
+       /*
+-       * Have we reached the end of the compression block or the end of the
+-       * decompressed data?  The latter can happen for example if the current
+-       * position in the compression block is one byte before its end so the
+-       * first two checks do not detect it.
++       * If the output sub-block filled before the compressed sub-block input
++       * was exhausted, skip the remaining compressed bytes in this sub-block.
+        */
+-      if (cb == cb_end || !le16_to_cpup((le16*)cb) || dest == dest_end) {
+-              if (dest_end > dest)
+-                      memset(dest, 0, dest_end - dest);
+-              ntfs_log_debug("Completed. Returning success (0).\n");
+-              return 0;
+-      }
+-      /* Setup offset for the current sub-block destination. */
+-      dest_sb_start = dest;
+-      dest_sb_end = dest + NTFS_SB_SIZE;
+-      /* Check that we are still within allowed boundaries. */
+-      if (dest_sb_end > dest_end)
+-              goto return_overflow;
+-      /* Does the minimum size of a compressed sb overflow valid range? */
+-      if (cb + 6 > cb_end)
+-              goto return_overflow;
+-      /* Setup the current sub-block source pointers and validate range. */
+-      cb_sb_start = cb;
+-      cb_sb_end = cb_sb_start + (le16_to_cpup((le16*)cb) & NTFS_SB_SIZE_MASK)
+-                      + 3;
+-      if (cb_sb_end > cb_end)
+-              goto return_overflow;
+-      /* Now, we are ready to process the current sub-block (sb). */
+-      if (!(le16_to_cpup((le16*)cb) & NTFS_SB_IS_COMPRESSED)) {
+-              ntfs_log_debug("Found uncompressed sub-block.\n");
+-              /* This sb is not compressed, just copy it into destination. */
+-              /* Advance source position to first data byte. */
+-              cb += 2;
+-              /* An uncompressed sb must be full size. */
+-              if (cb_sb_end - cb != NTFS_SB_SIZE)
+-                      goto return_overflow;
+-              /* Copy the block and advance the source position. */
+-              memcpy(dest, cb, NTFS_SB_SIZE);
+-              cb += NTFS_SB_SIZE;
+-              /* Advance destination position to next sub-block. */
+-              dest += NTFS_SB_SIZE;
+-              goto do_next_sb;
+-      }
+-      ntfs_log_debug("Found compressed sub-block.\n");
+-      /* This sb is compressed, decompress it into destination. */
+-      /* Forward to the first tag in the sub-block. */
+-      cb += 2;
+-do_next_tag:
+-      if (cb == cb_sb_end) {
+-              /* Check if the decompressed sub-block was not full-length. */
+-              if (dest < dest_sb_end) {
+-                      int nr_bytes = dest_sb_end - dest;
+-
+-                      ntfs_log_debug("Filling incomplete sub-block with 
zeroes.\n");
+-                      /* Zero remainder and update destination position. */
+-                      memset(dest, 0, nr_bytes);
+-                      dest += nr_bytes;
+-              }
+-              /* We have finished the current sub-block. */
+-              goto do_next_sb;
+-      }
+-      /* Check we are still in range. */
+-      if (cb > cb_sb_end || dest >= dest_sb_end)
+-              goto return_overflow;
+-      /* Get the next tag and advance to first token. */
+-      tag = *cb++;
+-      /* Parse the eight tokens described by the tag. */
+-      for (token = 0; token < 8; token++, tag >>= 1) {
+-              u16 lg, pt, length, max_non_overlap;
+-              register u16 i;
+-              u8 *dest_back_addr;
+-
+-              /* Check if we are done / still in range. */
+-              if (cb >= cb_sb_end || dest >= dest_sb_end)
+-                      break;
+-              /* Determine token type and parse appropriately.*/
+-              if ((tag & NTFS_TOKEN_MASK) == NTFS_SYMBOL_TOKEN) {
+-                      /*
+-                       * We have a symbol token, copy the symbol across, and
+-                       * advance the source and destination positions.
+-                       */
+-                      *dest++ = *cb++;
+-                      /* Continue with the next token. */
+-                      continue;
+-              }
+-              /*
+-               * We have a phrase token. Make sure it is not the first tag in
+-               * the sb as this is illegal and would confuse the code below.
+-               */
+-              if (dest == dest_sb_start)
+-                      goto return_overflow;
+-              /*
+-               * Determine the number of bytes to go back (p) and the number
+-               * of bytes to copy (l). We use an optimized algorithm in which
+-               * we first calculate log2(current destination position in sb),
+-               * which allows determination of l and p in O(1) rather than
+-               * O(n). We just need an arch-optimized log2() function now.
+-               */
+-              lg = 0;
+-              for (i = dest - dest_sb_start - 1; i >= 0x10; i >>= 1)
+-                      lg++;
+-              /* Get the phrase token into i. */
+-              pt = le16_to_cpup((le16*)cb);
+-              /*
+-               * Calculate starting position of the byte sequence in
+-               * the destination using the fact that p = (pt >> (12 - lg)) + 1
+-               * and make sure we don't go too far back.
+-               */
+-              dest_back_addr = dest - (pt >> (12 - lg)) - 1;
+-              if (dest_back_addr < dest_sb_start)
+-                      goto return_overflow;
+-              /* Now calculate the length of the byte sequence. */
+-              length = (pt & (0xfff >> lg)) + 3;
+-              /* Verify destination is in range. */
+-              if (dest + length > dest_sb_end)
+-                      goto return_overflow;
+-              /* The number of non-overlapping bytes. */
+-              max_non_overlap = dest - dest_back_addr;
+-              if (length <= max_non_overlap) {
+-                      /* The byte sequence doesn't overlap, just copy it. */
+-                      memcpy(dest, dest_back_addr, length);
+-                      /* Advance destination pointer. */
+-                      dest += length;
+-              } else {
+-                      /*
+-                       * The byte sequence does overlap, copy non-overlapping
+-                       * part and then do a slow byte by byte copy for the
+-                       * overlapping part. Also, advance the destination
+-                       * pointer.
+-                       */
+-                      memcpy(dest, dest_back_addr, max_non_overlap);
+-                      dest += max_non_overlap;
+-                      dest_back_addr += max_non_overlap;
+-                      length -= max_non_overlap;
+-                      while (length--)
+-                              *dest++ = *dest_back_addr++;
+-              }
+-              /* Advance source position and continue with the next token. */
+-              cb += 2;
+-      }
+-      /* No tokens left in the current tag. Continue with the next tag. */
+-      goto do_next_tag;
++      cb = cb_sb_end;
++      goto do_next_sb;
++complete:
++      if (dest_end > dest)
++              memset(dest, 0, dest_end - dest);
++      ntfs_log_debug("Completed. Returning success (0).\n");
++      return 0;
+ return_overflow:
+       errno = EOVERFLOW;
+       ntfs_log_perror("Failed to decompress file");
diff -Nru ntfs-3g-2022.10.3/debian/patches/CVE-2026-42618-e021cbe3.patch 
ntfs-3g-2022.10.3/debian/patches/CVE-2026-42618-e021cbe3.patch
--- ntfs-3g-2022.10.3/debian/patches/CVE-2026-42618-e021cbe3.patch      
1969-12-31 18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/CVE-2026-42618-e021cbe3.patch      
2026-11-01 16:08:25.000000000 -0600
@@ -0,0 +1,42 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/e021cbe3d7a42963110e4561f2a1327614c20a00
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From e021cbe3d7a42963110e4561f2a1327614c20a00 Mon Sep 17 00:00:00 2001
+From: Erik Larsson <[email protected]>
+Date: Fri, 24 Apr 2026 12:06:24 +0300
+Subject: [PATCH] compress.c: Fix incorrect comparison in destination bounds
+ check.
+
+When checking whether 'dest' was at or past the end of the destination
+buffer we were checking if 'dest' was larger then 'dest_sb_end', however
+'dest_sb_end' is already past the end of the destination buffer since
+it is constructed with 'dest + dest_size', i.e. dereferencing
+'dest_sb_end' is invalid. Fixed by using '>=' instead of '>'.
+
+Thanks to Alexandro Calo' from Nozomi Networks Labs for reporting this
+issue.
+
+diff --git a/libntfs-3g/compress.c b/libntfs-3g/compress.c
+--- a/libntfs-3g/compress.c
++++ b/libntfs-3g/compress.c
+@@ -546,7 +546,7 @@
+               goto do_next_sb;
+       }
+       /* Check we are still in range. */
+-      if (cb > cb_sb_end || dest > dest_sb_end)
++      if (cb > cb_sb_end || dest >= dest_sb_end)
+               goto return_overflow;
+       /* Get the next tag and advance to first token. */
+       tag = *cb++;
+@@ -557,7 +557,7 @@
+               u8 *dest_back_addr;
+ 
+               /* Check if we are done / still in range. */
+-              if (cb >= cb_sb_end || dest > dest_sb_end)
++              if (cb >= cb_sb_end || dest >= dest_sb_end)
+                       break;
+               /* Determine token type and parse appropriately.*/
+               if ((tag & NTFS_TOKEN_MASK) == NTFS_SYMBOL_TOKEN) {
diff -Nru ntfs-3g-2022.10.3/debian/patches/CVE-2026-46569.patch 
ntfs-3g-2022.10.3/debian/patches/CVE-2026-46569.patch
--- ntfs-3g-2022.10.3/debian/patches/CVE-2026-46569.patch       1969-12-31 
18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/CVE-2026-46569.patch       2026-11-01 
16:08:25.000000000 -0600
@@ -0,0 +1,59 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/961ed5c2fa2f36ef6a347f04e1fa586113c97e7c
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From 961ed5c2fa2f36ef6a347f04e1fa586113c97e7c Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Mon, 8 Jun 2026 10:34:38 +0300
+Subject: [PATCH] Bound tail_size in ntfs_ib_copy_tail against destination
+ capacity.
+
+A crafted source INDX block can drive tail_size past the freshly
+allocated destination's usable space and overflow the memcpy.  Add
+a capacity check (and a < 0 guard against sign-extension at
+memcpy).
+
+Thanks to Alexandro Calo' from Nozomi Networks Labs for reporting
+this issue.
+
+diff --git a/libntfs-3g/index.c b/libntfs-3g/index.c
+index e48d6aafb..6ebaa2e4a 100644
+--- a/libntfs-3g/index.c
++++ b/libntfs-3g/index.c
+@@ -1144,7 +1144,7 @@ static int ntfs_ib_copy_tail(ntfs_index_context *icx, 
INDEX_BLOCK *src,
+ {
+       u8 *ies_end;
+       INDEX_ENTRY *ie_head;           /* first entry after the median */
+-      int tail_size, ret;
++      int tail_size, dst_capacity, ret;
+       INDEX_BLOCK *dst;
+       
+       ntfs_log_trace("Entering\n");
+@@ -1158,6 +1158,25 @@ static int ntfs_ib_copy_tail(ntfs_index_context *icx, 
INDEX_BLOCK *src,
+       
+       ies_end = (u8 *)ntfs_ie_get_end(&src->index);
+       tail_size = ies_end - (u8 *)ie_head;
++      dst_capacity = (int)(le32_to_cpu(dst->index.allocated_size)
++                           - le32_to_cpu(dst->index.entries_offset));
++
++      /*
++       * src->index.entries_offset (on-disk, only required to be >=
++       * sizeof(INDEX_HEADER) by ntfs_index_block_inconsistent) may be smaller
++       * than dst's, which ntfs_ib_alloc fixes (40 on a 4 KiB block).  When
++       * the median lands near the start of the source stream the gap lets
++       * tail_size exceed dst's usable space.  The < 0 guard catches a
++       * negative tail_size before memcpy sign-extends it into a huge size_t.
++       */
++      if (tail_size < 0 || tail_size > dst_capacity) {
++              ntfs_log_error("Invalid tail_size %d (dst capacity %d) in "
++                              "ntfs_ib_copy_tail\n", tail_size, dst_capacity);
++              free(dst);
++              errno = EIO;
++              return STATUS_ERROR;
++      }
++
+       memcpy(ntfs_ie_get_first(&dst->index), ie_head, tail_size);
+       
+       dst->index.index_length = cpu_to_le32(tail_size + 
diff -Nru ntfs-3g-2022.10.3/debian/patches/CVE-2026-46570.patch 
ntfs-3g-2022.10.3/debian/patches/CVE-2026-46570.patch
--- ntfs-3g-2022.10.3/debian/patches/CVE-2026-46570.patch       1969-12-31 
18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/CVE-2026-46570.patch       2026-11-01 
16:08:25.000000000 -0600
@@ -0,0 +1,39 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/5ab37421ebbcbf33bf96876c7a034393bda05a25
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From 5ab37421ebbcbf33bf96876c7a034393bda05a25 Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Mon, 8 Jun 2026 14:24:54 +0300
+Subject: [PATCH] Cap B tree depth in ntfs_index_walk_down to MAX_PARENT_VCN.
+
+ntfs_index_walk_down() incremented ictx->pindex and wrote into
+ictx->parent_pos[] / parent_vcn[] (both sized MAX_PARENT_VCN) without
+checking the bound.  A B+tree deeper than MAX_PARENT_VCN levels then
+overflowed both arrays and since parent_vcn[32] overlaps the pindex
+field the OOB write gave an attacker control over the next iteration's
+offset and value.
+
+Thanks to Alexandro Calo' from Nozomi Networks Labs for reporting this
+issue.
+
+diff --git a/libntfs-3g/index.c b/libntfs-3g/index.c
+index e48d6aafb..4768128d2 100644
+--- a/libntfs-3g/index.c
++++ b/libntfs-3g/index.c
+@@ -2056,6 +2056,13 @@ static INDEX_ENTRY *ntfs_index_walk_down(INDEX_ENTRY 
*ie,
+                       /* down from non-zero level */
+                       
+                       ictx->pindex++;
++                      if (ictx->pindex >= MAX_PARENT_VCN) {
++                              errno = EOPNOTSUPP;
++                              ntfs_log_perror("Index is over %d level deep",
++                                              MAX_PARENT_VCN);
++                              entry = (INDEX_ENTRY*)NULL;
++                              break;
++                      }
+               }
+               ictx->parent_pos[ictx->pindex] = 0;
+               ictx->parent_vcn[ictx->pindex] = vcn;
diff -Nru ntfs-3g-2022.10.3/debian/patches/CVE-2026-46571.patch 
ntfs-3g-2022.10.3/debian/patches/CVE-2026-46571.patch
--- ntfs-3g-2022.10.3/debian/patches/CVE-2026-46571.patch       1969-12-31 
18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/CVE-2026-46571.patch       2026-11-01 
16:08:25.000000000 -0600
@@ -0,0 +1,47 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/b912272b30c552b0457e40a0a68b1fac2587aaea
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From b912272b30c552b0457e40a0a68b1fac2587aaea Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Mon, 8 Jun 2026 14:16:39 +0300
+Subject: [PATCH] reparse.c: Bounds-check locase index in ntfs_fix_file_name.
+
+The case-insensitive branch indexed vol->locase with a raw 16-bit code
+point and no check against vol->upcase_len.  A truncated $UpCase made
+the table shorter than 65536 entries, so a file name character >=
+upcase_len read past it and leaked 2 heap bytes via the symlink target
+returned to userspace.
+
+Thanks to Alexandro Calo' from Nozomi Networks Labs for reporting this
+issue.
+
+diff --git a/libntfs-3g/reparse.c b/libntfs-3g/reparse.c
+index 76dbe2f1b..54858caa7 100644
+--- a/libntfs-3g/reparse.c
++++ b/libntfs-3g/reparse.c
+@@ -196,11 +196,19 @@ static u64 ntfs_fix_file_name(ntfs_inode *dir_ni, 
ntfschar *uname,
+                               lemref = entry->indexed_file;
+                               mref = le64_to_cpu(lemref);
+                               if (NVolCaseSensitive(vol) || !vol->locase) {
+-                                      for (i=0; i<found->file_name_length; 
i++)
++                                      for (i=0; i<found->file_name_length;
++                                                      i++)
+                                               uname[i] = found->file_name[i];
+                               } else {
+-                                      for (i=0; i<found->file_name_length; 
i++)
+-                                              uname[i] = 
vol->locase[le16_to_cpu(found->file_name[i])];
++                                      for (i=0; i<found->file_name_length;
++                                                      i++) {
++                                              u16 u = le16_to_cpu(found->
++                                                              file_name[i]);
++                                              uname[i] = (u < vol->upcase_len)
++                                                              ? vol->locase[u]
++                                                              : found->
++                                                                file_name[i];
++                                      }
+                               }
+                       }
+               }
diff -Nru ntfs-3g-2022.10.3/debian/patches/CVE-2026-46572.patch 
ntfs-3g-2022.10.3/debian/patches/CVE-2026-46572.patch
--- ntfs-3g-2022.10.3/debian/patches/CVE-2026-46572.patch       1969-12-31 
18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/CVE-2026-46572.patch       2026-11-01 
16:08:25.000000000 -0600
@@ -0,0 +1,84 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/dfd1a7c6eb23a22238ec193451fa59c989b4381f
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From dfd1a7c6eb23a22238ec193451fa59c989b4381f Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Mon, 8 Jun 2026 09:27:45 +0300
+Subject: [PATCH] Validate index entry stream while reading from disk.
+
+Add ntfs_ie_stream_inconsistent() to walk the IE stream once and verify
+every entry: header fits, length is sane and 8-byte aligned, entry fits,
+and the END entry terminates the stream exactly.  Call it from
+ntfs_attr_inconsistent() for INDEX_ROOT and from
+ntfs_index_block_inconsistent() for INDX blocks so callers can rely on
+the IE stream being well-formed.
+
+
+diff --git a/include/ntfs-3g/index.h b/include/ntfs-3g/index.h
+--- a/include/ntfs-3g/index.h
++++ b/include/ntfs-3g/index.h
+@@ -143,6 +143,7 @@
+                       u64 inum, VCN vcn);
+ extern int ntfs_index_entry_inconsistent(const INDEX_ENTRY *ie,
+                       COLLATION_RULES collation_rule, u64 inum);
++extern int ntfs_ie_stream_inconsistent(const INDEX_HEADER *ih, u64 inum);
+ extern int ntfs_index_lookup(const void *key, const int key_len,
+               ntfs_index_context *ictx) __attribute_warn_unused_result__;
+ 
+diff --git a/libntfs-3g/index.c b/libntfs-3g/index.c
+--- a/libntfs-3g/index.c
++++ b/libntfs-3g/index.c
+@@ -504,6 +504,8 @@
+                               (unsigned long long)inum);
+               return -1;
+       }
++      if (ntfs_ie_stream_inconsistent(&ib->index, inum))
++              return -1;
+ 
+       return (0);
+ }
+@@ -560,7 +562,40 @@
+       return (ret);
+ }
+ 
+-/** 
++int ntfs_ie_stream_inconsistent(const INDEX_HEADER *ih, u64 inum)
++{
++      const u8 *ies_start = (const u8 *)ih + le32_to_cpu(ih->entries_offset);
++      const u8 *ies_end = (const u8 *)ih + le32_to_cpu(ih->index_length);
++      const u8 *ie;
++
++      ntfs_log_trace("Entering\n");
++
++      for (ie = ies_start; ie < ies_end; ) {
++              u32 len;
++              const INDEX_ENTRY *ent = (const INDEX_ENTRY *)ie;
++
++              if ((size_t)(ies_end - ie) < sizeof(INDEX_ENTRY_HEADER))
++                      goto err;
++              len = le16_to_cpu(ent->length);
++              if (len < sizeof(INDEX_ENTRY_HEADER) || (len & 7))
++                      goto err;
++              if ((size_t)(ies_end - ie) < len)
++                      goto err;
++              if (ent->ie_flags & INDEX_ENTRY_END) {
++                      /* END must terminate the stream exactly. */
++                      if (ie + len != ies_end)
++                              goto err;
++                      return 0;
++              }
++              ie += len;
++      }
++err:
++      ntfs_log_error("Corrupt index entry stream in inode %lld\n",
++                      (long long)inum);
++      return -1;
++}
++
++/**
+  * Find a key in the index block.
+  * 
+  * Return values:
diff -Nru ntfs-3g-2022.10.3/debian/patches/CVE-2026-56135-21f37bb5.patch 
ntfs-3g-2022.10.3/debian/patches/CVE-2026-56135-21f37bb5.patch
--- ntfs-3g-2022.10.3/debian/patches/CVE-2026-56135-21f37bb5.patch      
1969-12-31 18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/CVE-2026-56135-21f37bb5.patch      
2026-11-01 16:08:25.000000000 -0600
@@ -0,0 +1,32 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/21f37bb5efa874b4e1212f499ca643d69f024021
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+From 21f37bb5efa874b4e1212f499ca643d69f024021 Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Wed, 10 Jun 2026 09:12:22 +0300
+Subject: [PATCH] ACLs: reject ACEs smaller than the ACE header in valid_acl
+
+valid_acl() walks the on-disk security descriptor and, for each ACE,
+switches on pace->type before any check that pace->size is at least
+sizeof(ACE_HEADER).  Every per-type case then performs SID reads and
+size arithmetic that assume an ACE_HEADER is fully present.
+
+Reject any ACE whose size is below sizeof(ACE_HEADER) up front so the
+per-type logic always operates on a fully-present header.
+
+diff --git a/libntfs-3g/acls.c b/libntfs-3g/acls.c
+index 150a6a7e..5f45dad9 100644
+--- a/libntfs-3g/acls.c
++++ b/libntfs-3g/acls.c
+@@ -560,7 +560,9 @@ static BOOL valid_acl(const ACL *pacl, unsigned int end)
+                       pace = (const ACCESS_ALLOWED_ACE*)
+                               &((const char*)pacl)[offace];
+                       acesz = le16_to_cpu(pace->size);
+-                      switch (pace->type) {
++                      if (acesz < sizeof(ACE_HEADER))
++                              ok = FALSE;
++                      else switch (pace->type) {
+                       case ACCESS_ALLOWED_ACE_TYPE :
+                       case ACCESS_DENIED_ACE_TYPE :
+                               wantsz = ntfs_sid_size(&pace->sid) + 8;
diff -Nru ntfs-3g-2022.10.3/debian/patches/CVE-2026-56135-289ebcf3.patch 
ntfs-3g-2022.10.3/debian/patches/CVE-2026-56135-289ebcf3.patch
--- ntfs-3g-2022.10.3/debian/patches/CVE-2026-56135-289ebcf3.patch      
1969-12-31 18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/CVE-2026-56135-289ebcf3.patch      
2026-11-01 16:08:25.000000000 -0600
@@ -0,0 +1,134 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/289ebcf3dfbed422181cbfa524d75e32d9c59bd3
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+From 289ebcf3dfbed422181cbfa524d75e32d9c59bd3 Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Mon, 8 Jun 2026 21:37:03 +0300
+Subject: [PATCH] ACLs: Fix inherited ACL buffer sizing.
+
+build_inherited_id() allocates the security descriptor buffer before
+calling ntfs_inherit_acl() for the parent DACL and SACL.  The old size
+estimate only reserved fixed slack for a few creator-owner/group SID
+expansions.
+
+A crafted parent ACL can contain more ACCESS_DENIED CREATOR_OWNER ACEs
+with CONTAINER_INHERIT_ACE than that estimate covers.  During
+inheritance, each such ACE may be emitted once with CREATOR_OWNER
+replaced by the new owner SID and once again verbatim for further
+inheritance, allowing ntfs_inherit_acl() to write past the allocated
+descriptor buffer.
+
+Count the qualifying parent ACEs before allocation and add the required
+per-ACE slack for both DACL and SACL inheritance.
+
+Thanks to Alexandro Calo' from Nozomi Networks Labs for reporting this
+issue.
+
+diff --git a/include/ntfs-3g/acls.h b/include/ntfs-3g/acls.h
+index 932791e9..53e80876 100644
+--- a/include/ntfs-3g/acls.h
++++ b/include/ntfs-3g/acls.h
+@@ -168,6 +168,7 @@ char *ntfs_build_descr_posix(struct MAPPING* const 
mapping[],
+ 
+ #endif /* POSIXACLS */
+ 
++int ntfs_count_creator_owner_inherit(const ACL *acl);
+ int ntfs_inherit_acl(const ACL *oldacl, ACL *newacl,
+                       const SID *usid, const SID *gsid,
+                       BOOL fordir, le16 inherited);
+diff --git a/libntfs-3g/acls.c b/libntfs-3g/acls.c
+index 2d24d65e..150a6a7e 100644
+--- a/libntfs-3g/acls.c
++++ b/libntfs-3g/acls.c
+@@ -681,6 +681,53 @@ BOOL ntfs_valid_descr(const char *securattr, unsigned int 
attrsz)
+       return (ok);
+ }
+ 
++/**
++ * ntfs_count_creator_owner_inherit: count creator-owner inheritable ACEs
++ * @acl: ACL to scan
++ *
++ * Walks @acl bounded by acl->size, counting DENY ACEs whose SID matches the
++ * creator-owner placeholder and which carry the CONTAINER_INHERIT_ACE flag.
++ * A zero, too-small, or oversized ace->size terminates the walk so a 
malformed
++ * ACL cannot drive an out-of-bounds read.
++ *
++ * Return: the count, or 0 if @acl is NULL or its header does not fit.
++ */
++
++int ntfs_count_creator_owner_inherit(const ACL *acl)
++{
++      const ACCESS_ALLOWED_ACE *ace;
++      unsigned int off;
++      unsigned int acl_size;
++      unsigned int acesz;
++      int oldcnt;
++      int nace;
++      int count;
++
++      count = 0;
++      if (!acl)
++              return (0);
++      acl_size = le16_to_cpu(acl->size);
++      if (acl_size < sizeof(ACL))
++              return (0);
++      oldcnt = le16_to_cpu(acl->ace_count);
++      off = sizeof(ACL);
++      for (nace = 0; nace < oldcnt; nace++) {
++              if (off + 8 > acl_size)
++                      break;
++              ace = (const ACCESS_ALLOWED_ACE*)((const char*)acl + off);
++              acesz = le16_to_cpu(ace->size);
++              if (acesz < 8 || acesz > acl_size - off)
++                      break;
++              if (ace->type == ACCESS_DENIED_ACE_TYPE
++                              && (ace->flags & CONTAINER_INHERIT_ACE)
++                              && acesz >= 8 + sizeof(ownersidbytes)
++                              && ntfs_same_sid(&ace->sid, ownersid))
++                      count++;
++              off += acesz;
++      }
++      return count;
++}
++
+ /*
+  *            Copy the inheritable parts of an ACL
+  *
+diff --git a/libntfs-3g/security.c b/libntfs-3g/security.c
+index acee0a5e..f26f549a 100644
+--- a/libntfs-3g/security.c
++++ b/libntfs-3g/security.c
+@@ -3944,6 +3944,32 @@ static le32 build_inherited_id(struct SECURITY_CONTEXT 
*scx,
+       usidsz = ntfs_sid_size(usid);
+       gsidsz = ntfs_sid_size(gsid);
+       newattrsz = parentattrsz + 3*usidsz + 3*gsidsz;
++      /*
++       * The +3*usidsz + 3*gsidsz slack above only covers three creator-owner
++       * SID expansions during ntfs_inherit_acl().
++       * Add exact slack for the actual number of expanding ACEs in both the
++       * parent DACL and SACL.  Each such ACE may grow the output by at most
++       * (usidsz - 12) bytes for the SID substitution plus 20 bytes of
++       * original ACE..kept for grandkids.
++       */
++      if (pphead->dacl) {
++              offpacl = le32_to_cpu(pphead->dacl);
++              if ((unsigned int)offpacl + sizeof(ACL) <=
++                              (unsigned int)parentattrsz) {
++                      ppacl = (const ACL*)&parentattr[offpacl];
++                      newattrsz += ntfs_count_creator_owner_inherit(ppacl)
++                                      * (usidsz - 12 + 20);
++              }
++      }
++      if (pphead->sacl) {
++              offpacl = le32_to_cpu(pphead->sacl);
++              if ((unsigned int)offpacl + sizeof(ACL) <=
++                              (unsigned int)parentattrsz) {
++                      ppacl = (const ACL*)&parentattr[offpacl];
++                      newattrsz += ntfs_count_creator_owner_inherit(ppacl)
++                                      * (usidsz - 12 + 20);
++              }
++      }
+       if (fordir)
+               newattrsz *= 2;
+       newattr = (char*)ntfs_malloc(newattrsz);
diff -Nru ntfs-3g-2022.10.3/debian/patches/CVE-2026-56135-b0b9c32c.patch 
ntfs-3g-2022.10.3/debian/patches/CVE-2026-56135-b0b9c32c.patch
--- ntfs-3g-2022.10.3/debian/patches/CVE-2026-56135-b0b9c32c.patch      
1969-12-31 18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/CVE-2026-56135-b0b9c32c.patch      
2026-11-01 16:08:25.000000000 -0600
@@ -0,0 +1,197 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/b0b9c32c7ff0fb0eac1eb9272da2ed10ae51a3b9
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+From b0b9c32c7ff0fb0eac1eb9272da2ed10ae51a3b9 Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Wed, 10 Jun 2026 09:24:04 +0300
+Subject: [PATCH] ACLs: size inherited ACLs generically
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The previous commit targets the overflow bug report directly: count DENY
+ACEs with CONTAINER_INHERIT_ACE that reference CREATOR_OWNER and reserve
+per match slack. But it doesn't cover all cases.
+
+An ACE has an 8-byte fixed part plus a variable-length SID.
+ntfs_inherit_acl() replaces creator placeholders (the 12-byte
+CREATOR_OWNER and CREATOR_GROUP SIDs) with the real owner or group SID,
+often larger, so substituted ACEs grow.  That substitution runs in more
+shapes than the previous counter recognises: for both ACCESS_ALLOWED and
+ACCESS_DENIED ACEs, for both CREATOR_OWNER and CREATOR_GROUP, and
+regardless of any inheritance flag whenever the parent descriptor's
+auto-inherited bit is clear.  And when the resolved owner SID happens to
+equal the static creator-group SID (S-1-3-1), a single ACE is
+substituted twice in the same pass — once as owner, once as group —
+doubling its growth.  Each of those uncovered shapes can under-allocate
+at the same write site as the originally reported issue.
+
+Replace the counter with ntfs_inherit_acl_extra_size(), which covers all
+four ALLOW/DENY x OWNER/GROUP combinations, reserves slack for the
+double-substitution case, validates each ACE's SID structurally before
+matching, and returns bytes directly so build_inherited_id() no longer
+encodes the per-ACE growth formula.
+
+Thanks to Alexandro Calo' from Nozomi Networks Labs.
+
+diff --git a/include/ntfs-3g/acls.h b/include/ntfs-3g/acls.h
+index 53e80876..4f36389c 100644
+--- a/include/ntfs-3g/acls.h
++++ b/include/ntfs-3g/acls.h
+@@ -168,7 +168,8 @@ char *ntfs_build_descr_posix(struct MAPPING* const 
mapping[],
+ 
+ #endif /* POSIXACLS */
+ 
+-int ntfs_count_creator_owner_inherit(const ACL *acl);
++int ntfs_inherit_acl_extra_size(const ACL *acl, const SID *usid,
++                      const SID *gsid);
+ int ntfs_inherit_acl(const ACL *oldacl, ACL *newacl,
+                       const SID *usid, const SID *gsid,
+                       BOOL fordir, le16 inherited);
+diff --git a/libntfs-3g/acls.c b/libntfs-3g/acls.c
+index 5f45dad9..21e9682f 100644
+--- a/libntfs-3g/acls.c
++++ b/libntfs-3g/acls.c
+@@ -684,34 +684,49 @@ BOOL ntfs_valid_descr(const char *securattr, unsigned 
int attrsz)
+ }
+ 
+ /**
+- * ntfs_count_creator_owner_inherit: count creator-owner inheritable ACEs
++ * ntfs_inherit_acl_extra_size: compute creator SID inheritance slack
+  * @acl: ACL to scan
++ * @usid: owner SID to substitute for CREATOR_OWNER
++ * @gsid: group SID to substitute for CREATOR_GROUP
+  *
+- * Walks @acl bounded by acl->size, counting DENY ACEs whose SID matches the
+- * creator-owner placeholder and which carry the CONTAINER_INHERIT_ACE flag.
+- * A zero, too-small, or oversized ace->size terminates the walk so a 
malformed
+- * ACL cannot drive an out-of-bounds read.
++ * Walks @acl bounded by acl->size, adding slack for ALLOW and DENY ACEs
++ * whose SID matches either creator placeholder.  ntfs_inherit_acl() can
++ * replace those placeholders by @usid or @gsid and, for directories, can
++ * also keep a verbatim copy for child inheritance.  Count the worst-case
++ * extra bytes so the inherited descriptor allocation cannot be overrun.
+  *
+- * Return: the count, or 0 if @acl is NULL or its header does not fit.
++ * Return: extra bytes needed, or 0 if @acl is NULL or structurally rejected.
+  */
+ 
+-int ntfs_count_creator_owner_inherit(const ACL *acl)
++int ntfs_inherit_acl_extra_size(const ACL *acl,
++                      const SID *usid, const SID *gsid)
+ {
+       const ACCESS_ALLOWED_ACE *ace;
+       unsigned int off;
+       unsigned int acl_size;
+       unsigned int acesz;
++      unsigned int sidsz;
++      int usidsz;
++      int gsidsz;
++      int ownersidsz;
++      int groupsidsz;
+       int oldcnt;
+       int nace;
+-      int count;
++      int extra;
++      BOOL usid_is_group_sid;
+ 
+-      count = 0;
+-      if (!acl)
++      extra = 0;
++      if (!acl || !usid || !gsid)
+               return (0);
+       acl_size = le16_to_cpu(acl->size);
+       if (acl_size < sizeof(ACL))
+               return (0);
+       oldcnt = le16_to_cpu(acl->ace_count);
++      usidsz = ntfs_sid_size(usid);
++      gsidsz = ntfs_sid_size(gsid);
++      ownersidsz = sizeof(ownersidbytes);
++      groupsidsz = sizeof(groupsidbytes);
++      usid_is_group_sid = ntfs_same_sid(usid, groupsid);
+       off = sizeof(ACL);
+       for (nace = 0; nace < oldcnt; nace++) {
+               if (off + 8 > acl_size)
+@@ -720,14 +735,33 @@ int ntfs_count_creator_owner_inherit(const ACL *acl)
+               acesz = le16_to_cpu(ace->size);
+               if (acesz < 8 || acesz > acl_size - off)
+                       break;
+-              if (ace->type == ACCESS_DENIED_ACE_TYPE
+-                              && (ace->flags & CONTAINER_INHERIT_ACE)
+-                              && acesz >= 8 + sizeof(ownersidbytes)
+-                              && ntfs_same_sid(&ace->sid, ownersid))
+-                      count++;
++              switch (ace->type) {
++              case ACCESS_ALLOWED_ACE_TYPE :
++              case ACCESS_DENIED_ACE_TYPE :
++                      if ((acesz >= 8 + sizeof(ownersidbytes))
++                                      && ntfs_valid_sid(&ace->sid)) {
++                              sidsz = ntfs_sid_size(&ace->sid);
++                              if (sidsz <= acesz - 8) {
++                                      if (ntfs_same_sid(&ace->sid, ownersid))
++                                      {
++                                              extra += usidsz - ownersidsz +
++                                                              20;
++                                              if (usid_is_group_sid)
++                                                      extra += gsidsz -
++                                                              groupsidsz + 20;
++                                      }
++                                      if (ntfs_same_sid(&ace->sid, groupsid))
++                                              extra += gsidsz - groupsidsz +
++                                                              20;
++                              }
++                      }
++                      break;
++              default :
++                      break;
++              }
+               off += acesz;
+       }
+-      return count;
++      return extra;
+ }
+ 
+ /*
+diff --git a/libntfs-3g/security.c b/libntfs-3g/security.c
+index f26f549a..d0e030e1 100644
+--- a/libntfs-3g/security.c
++++ b/libntfs-3g/security.c
+@@ -3945,20 +3945,18 @@ static le32 build_inherited_id(struct SECURITY_CONTEXT 
*scx,
+       gsidsz = ntfs_sid_size(gsid);
+       newattrsz = parentattrsz + 3*usidsz + 3*gsidsz;
+       /*
+-       * The +3*usidsz + 3*gsidsz slack above only covers three creator-owner
+-       * SID expansions during ntfs_inherit_acl().
+-       * Add exact slack for the actual number of expanding ACEs in both the
+-       * parent DACL and SACL.  Each such ACE may grow the output by at most
+-       * (usidsz - 12) bytes for the SID substitution plus 20 bytes of
+-       * original ACE..kept for grandkids.
++       * The +3*usidsz + 3*gsidsz slack above only covers a few creator SID
++       * expansions during ntfs_inherit_acl().  Add worst-case slack for every
++       * ALLOW/DENY creator-owner and creator-group ACE in both the parent
++       * DACL and SACL.
+        */
+       if (pphead->dacl) {
+               offpacl = le32_to_cpu(pphead->dacl);
+               if ((unsigned int)offpacl + sizeof(ACL) <=
+                               (unsigned int)parentattrsz) {
+                       ppacl = (const ACL*)&parentattr[offpacl];
+-                      newattrsz += ntfs_count_creator_owner_inherit(ppacl)
+-                                      * (usidsz - 12 + 20);
++                      newattrsz += ntfs_inherit_acl_extra_size(ppacl,
++                                      usid, gsid);
+               }
+       }
+       if (pphead->sacl) {
+@@ -3966,8 +3964,8 @@ static le32 build_inherited_id(struct SECURITY_CONTEXT 
*scx,
+               if ((unsigned int)offpacl + sizeof(ACL) <=
+                               (unsigned int)parentattrsz) {
+                       ppacl = (const ACL*)&parentattr[offpacl];
+-                      newattrsz += ntfs_count_creator_owner_inherit(ppacl)
+-                                      * (usidsz - 12 + 20);
++                      newattrsz += ntfs_inherit_acl_extra_size(ppacl,
++                                      usid, gsid);
+               }
+       }
+       if (fordir)
diff -Nru ntfs-3g-2022.10.3/debian/patches/CVE-2026-56136.patch 
ntfs-3g-2022.10.3/debian/patches/CVE-2026-56136.patch
--- ntfs-3g-2022.10.3/debian/patches/CVE-2026-56136.patch       1969-12-31 
18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/CVE-2026-56136.patch       2026-11-01 
16:08:25.000000000 -0600
@@ -0,0 +1,42 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/edf2b892356684ca8f2fc5898943c7a9b90ffb80
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From edf2b892356684ca8f2fc5898943c7a9b90ffb80 Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Mon, 8 Jun 2026 09:57:11 +0300
+Subject: [PATCH] Validate INDEX_ROOT.index_block_size from disk.
+
+Reject INDEX_ROOT attributes whose index_block_size is smaller than
+NTFS_BLOCK_SIZE or not a power of two. Same rule the driver already
+applies at libntfs-3g/dir.c.
+
+Thanks to Alexandro Calo' from Nozomi Networks Labs for the report.
+
+
+diff --git a/libntfs-3g/attrib.c b/libntfs-3g/attrib.c
+--- a/libntfs-3g/attrib.c
++++ b/libntfs-3g/attrib.c
+@@ -3528,6 +3528,10 @@
+                       if (a->non_resident
+                           || (le32_to_cpu(a->value_length)
+                               < offsetof(INDEX_ROOT, index.reserved))
++                          || (le32_to_cpu(ir->index_block_size)
++                              < NTFS_BLOCK_SIZE)
++                          || (le32_to_cpu(ir->index_block_size)
++                              & (le32_to_cpu(ir->index_block_size) - 1))
+                           || (le32_to_cpu(ir->index.entries_offset)
+                               < sizeof(INDEX_HEADER))
+                           || (le32_to_cpu(ir->index.index_length)
+@@ -3540,6 +3544,9 @@
+                               ntfs_log_error("Corrupt index root"
+                                       " in MFT record %lld.\n",
+                                       (long long)inum);
++                              errno = EIO;
++                              ret = -1;
++                      } else if (ntfs_ie_stream_inconsistent(&ir->index, 
inum)) {
+                               errno = EIO;
+                               ret = -1;
+                       }
diff -Nru ntfs-3g-2022.10.3/debian/patches/GHSA-2c97-47cr-9xr8.patch 
ntfs-3g-2022.10.3/debian/patches/GHSA-2c97-47cr-9xr8.patch
--- ntfs-3g-2022.10.3/debian/patches/GHSA-2c97-47cr-9xr8.patch  1969-12-31 
18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/GHSA-2c97-47cr-9xr8.patch  2026-11-01 
16:08:25.000000000 -0600
@@ -0,0 +1,138 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/215f90547bc7cabb1e5af93c11a3c643c73d5fcc
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From 215f90547bc7cabb1e5af93c11a3c643c73d5fcc Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Thu, 20 Aug 2026 14:50:29 +0300
+Subject: [PATCH] Bounds check EA records in ntfs_ea_check_wsldev().
+
+ntfs_ea_check_wsldev() walked the EA buffer looking for a $LXDEV record
+without checking that the record fitted in the buffer. A crafted $EA
+attribute could make the trailing memcpy() read up to eight bytes past
+the end of the buffer and return them to user space through st_rdev.
+
+The same record check was already inlined in ntfs_set_ntfs_ea() so move
+it into a new ntfs_ea_inconsistent() and call it from both walkers.
+ntfs_ea_check_wsldev() also checks that the fixed header fits before
+using the record. ntfs_set_ntfs_ea() is unchanged.
+
+The record layout the check relies on is documented as
+FILE_FULL_EA_INFORMATION in
+https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_file_full_ea_information
+
+diff --git a/libntfs-3g/ea.c b/libntfs-3g/ea.c
+index b86930abd..05daa53d1 100644
+--- a/libntfs-3g/ea.c
++++ b/libntfs-3g/ea.c
+@@ -183,6 +183,60 @@ static int ntfs_update_ea(ntfs_inode *ni, const char 
*value, size_t size,
+       return (res);
+ }
+ 
++/**
++ * ntfs_ea_inconsistent - check that one EA record is sane
++ * @p_ea:     the EA record to check
++ * @offs:     where the record starts in the EA buffer
++ * @size:     size of the EA buffer
++ *
++ * Every EA record says where the next one starts. Check that the record fits
++ * in the buffer and that its name and value fit in the record :
++ *
++ *      offs                                                  nextoffs
++ *       |                                                        |
++ *       | header |      name      | NUL |     value      |  pad  |
++ *       |========|================|=====|================|=======|
++ *       |        |                |     |                |       |
++ *       |<- 8 -->|<- name_length->|<-1->|<-value_length->|<-0..3>|
++ *       |<---------------- next_entry_offset ------------------->|
++ *
++ * nextoffs is @offs + next_entry_offset. It has to move forward & be a
++ * multiple of 4 and stay inside the buffer. The name must not be empty and
++ * must be null terminated. Also the name and value must end at nextoffs or
++ * at most 3 bytes before it. The rest of it being padding.
++ *
++ * These are the checks which used to be inlined in ntfs_set_ntfs_ea(). The
++ * caller must have checked that @offs is inside the buffer and that the 8
++ * byte header fits.
++ *
++ * Return 0 if the record is sane and -1 if it is not.
++ */
++
++static int ntfs_ea_inconsistent(const EA_ATTR *p_ea, size_t offs, size_t size)
++{
++      size_t nextoffs;
++      BOOL ok;
++
++      nextoffs = offs + le32_to_cpu(p_ea->next_entry_offset);
++              /* null offset to next not allowed */
++      ok = (nextoffs > offs)
++                      && (nextoffs <= size)
++                      && !(nextoffs & 3)
++                      && p_ea->name_length
++                              /* zero sized value are allowed */
++                      && ((offs + offsetof(EA_ATTR,name)
++                              + p_ea->name_length + 1
++                              + le16_to_cpu(p_ea->value_length))
++                                      <= nextoffs)
++                      && ((offs + offsetof(EA_ATTR,name)
++                              + p_ea->name_length + 1
++                              + le16_to_cpu(p_ea->value_length))
++                                      >= (nextoffs - 3))
++                      && !p_ea->name[p_ea->name_length];
++      /* name not checked, as chkdsk accepts any chars */
++      return ok ? 0 : -1;
++}
++
+ /*
+  *            Return the existing EA
+  *
+@@ -263,22 +317,7 @@ int ntfs_set_ntfs_ea(ntfs_inode *ni, const char *value, 
size_t size, int flags)
+               while (ok && (offs < size)) {
+                       p_ea = (const EA_ATTR*)&value[offs];
+                       nextoffs = offs + le32_to_cpu(p_ea->next_entry_offset);
+-                              /* null offset to next not allowed */
+-                      ok = (nextoffs > offs)
+-                          && (nextoffs <= size)
+-                          && !(nextoffs & 3)
+-                          && p_ea->name_length
+-                              /* zero sized value are allowed */
+-                          && ((offs + offsetof(EA_ATTR,name)
+-                              + p_ea->name_length + 1
+-                              + le16_to_cpu(p_ea->value_length))
+-                                  <= nextoffs)
+-                          && ((offs + offsetof(EA_ATTR,name)
+-                              + p_ea->name_length + 1
+-                              + le16_to_cpu(p_ea->value_length))
+-                                  >= (nextoffs - 3))
+-                          && !p_ea->name[p_ea->name_length];
+-                      /* name not checked, as chkdsk accepts any chars */
++                      ok = !ntfs_ea_inconsistent(p_ea, offs, size);
+                       if (ok) {
+                               if (p_ea->flags & NEED_EA)
+                                       ea_count++;
+@@ -444,16 +483,20 @@ int ntfs_ea_check_wsldev(ntfs_inode *ni, dev_t *rdevp)
+               offset = 0;
+               found = FALSE;
+               do {
++                      /* The fixed header must fit in the remaining buffer. */
++                      if ((lth - offset) < (int)offsetof(EA_ATTR, name))
++                              break;
+                       p_ea = (const EA_ATTR*)&buf[offset];
++                      if (ntfs_ea_inconsistent(p_ea, offset, lth))
++                              break;
+                       next = le32_to_cpu(p_ea->next_entry_offset);
+-                      found = ((next > (int)(sizeof(lxdev) + sizeof(device)))
+-                              && (p_ea->name_length == (sizeof(lxdev) - 1))
++                      found = ((p_ea->name_length == (sizeof(lxdev) - 1))
+                               && (p_ea->value_length
+                                       == const_cpu_to_le16(sizeof(device)))
+                               && !memcmp(p_ea->name, lxdev, sizeof(lxdev)));
+                       if (!found)
+                               offset += next;
+-              } while (!found && (next > 0) && (offset < lth));
++              } while (!found && (offset < lth));
+               if (found) {
+                               /* beware of alignment */
+                       memcpy(&device, &p_ea->name[p_ea->name_length + 1],
diff -Nru ntfs-3g-2022.10.3/debian/patches/GHSA-jcjj-9262-6j6p.patch 
ntfs-3g-2022.10.3/debian/patches/GHSA-jcjj-9262-6j6p.patch
--- ntfs-3g-2022.10.3/debian/patches/GHSA-jcjj-9262-6j6p.patch  1969-12-31 
18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/GHSA-jcjj-9262-6j6p.patch  2026-11-01 
16:08:25.000000000 -0600
@@ -0,0 +1,102 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/1b938f54f731ba5046ed0a4439c666d7b11150eb
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From 1b938f54f731ba5046ed0a4439c666d7b11150eb Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Thu, 20 Aug 2026 16:21:22 +0300
+Subject: [PATCH] Reject corrupt attribute list entries when walking the list.
+
+ntfs_inode_attach_all_extents() and ntfs_attrlist_need() both walk the
+attribute list of an inode and advance by ale->length which comes from
+disk. Neither checked it. An entry with a zero length never advances the
+pointer so the walk spins forever and the mount stops responding. The
+loops also only require an entry to start inside the list so an entry
+near the end has its mft reference read past the end of it.
+
+The first function is reached when an attribute is added and the second
+when one is removed so both have to be fixed to close the hang. The
+attribute list itself is never checked when it is read. Only
+ntfs_external_attr_find() validates entries and it stops at the entry it
+is looking for so a bad entry after that one is never seen.
+
+Add ntfs_attrlist_entry_inconsistent() and call it from both walks. An
+entry has to be at least sizeof(ATTR_LIST_ENTRY) so that the walk makes
+progress and it has to lie fully within the list so that its fields can
+be read.
+
+diff --git a/include/ntfs-3g/attrlist.h b/include/ntfs-3g/attrlist.h
+index 2952e48b8..44027933f 100644
+--- a/include/ntfs-3g/attrlist.h
++++ b/include/ntfs-3g/attrlist.h
+@@ -26,6 +26,9 @@
+ 
+ #include "attrib.h"
+ 
++extern int ntfs_attrlist_entry_inconsistent(const ntfs_inode *ni,
++                              const ATTR_LIST_ENTRY *ale);
++
+ extern int ntfs_attrlist_need(ntfs_inode *ni);
+ 
+ extern int ntfs_attrlist_entry_add(ntfs_inode *ni, ATTR_RECORD *attr);
+diff --git a/libntfs-3g/attrlist.c b/libntfs-3g/attrlist.c
+index 29d408598..c06c0edb2 100644
+--- a/libntfs-3g/attrlist.c
++++ b/libntfs-3g/attrlist.c
+@@ -45,6 +45,32 @@
+ #include "logging.h"
+ #include "misc.h"
+ 
++/**
++ * ntfs_attrlist_entry_inconsistent - check an attribute list entry
++ * @ni:               inode whose attribute list is being walked
++ * @ale:      entry to check
++ *
++ * The entry has to lie fully within the attribute list so that its fields can
++ * be read, and it has to be long enough for a walk to make progress.
++ *
++ * Return 0 if the entry may be used and -1 if it may not, with errno set to
++ * EIO.
++ */
++int ntfs_attrlist_entry_inconsistent(const ntfs_inode *ni,
++                              const ATTR_LIST_ENTRY *ale)
++{
++      u32 length = le16_to_cpu(ale->length);
++
++      if ((length < sizeof(ATTR_LIST_ENTRY)) || (((const u8*)ale + length)
++                      > (ni->attr_list + ni->attr_list_size))) {
++              ntfs_log_error("Corrupt attribute list entry in inode %lld\n",
++                              (long long)ni->mft_no);
++              errno = EIO;
++              return -1;
++      }
++      return 0;
++}
++
+ /**
+  * ntfs_attrlist_need - check whether inode need attribute list
+  * @ni:               opened ntfs inode for which perform check
+@@ -85,6 +111,8 @@ int ntfs_attrlist_need(ntfs_inode *ni)
+       errno = 0;
+       ale = (ATTR_LIST_ENTRY *)ni->attr_list;
+       while ((u8*)ale < ni->attr_list + ni->attr_list_size) {
++              if (ntfs_attrlist_entry_inconsistent(ni, ale))
++                      return -1;
+               if (MREF_LE(ale->mft_reference) != ni->mft_no)
+                       return 1;
+               ale = (ATTR_LIST_ENTRY *)((u8*)ale + le16_to_cpu(ale->length));
+diff --git a/libntfs-3g/inode.c b/libntfs-3g/inode.c
+index 8819ff1dc..605b60dff 100644
+--- a/libntfs-3g/inode.c
++++ b/libntfs-3g/inode.c
+@@ -716,6 +716,8 @@ int ntfs_inode_attach_all_extents(ntfs_inode *ni)
+       errno = 0;
+       ale = (ATTR_LIST_ENTRY *)ni->attr_list;
+       while ((u8*)ale < ni->attr_list + ni->attr_list_size) {
++              if (ntfs_attrlist_entry_inconsistent(ni, ale))
++                      return -1;
+               if (ni->mft_no != MREF_LE(ale->mft_reference) &&
+                               prev_attached != MREF_LE(ale->mft_reference)) {
+                       if (!ntfs_extent_inode_open(ni, ale->mft_reference)) {
diff -Nru ntfs-3g-2022.10.3/debian/patches/GHSA-mc3c-983p-wqm8-056ce3a9.patch 
ntfs-3g-2022.10.3/debian/patches/GHSA-mc3c-983p-wqm8-056ce3a9.patch
--- ntfs-3g-2022.10.3/debian/patches/GHSA-mc3c-983p-wqm8-056ce3a9.patch 
1969-12-31 18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/GHSA-mc3c-983p-wqm8-056ce3a9.patch 
2026-11-01 16:08:25.000000000 -0600
@@ -0,0 +1,47 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/056ce3a90d995894bfd4ce282be7b66ebb20e7ac
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+From 056ce3a90d995894bfd4ce282be7b66ebb20e7ac Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Thu, 20 Aug 2026 13:15:20 +0300
+Subject: [PATCH] attrib.c: check the multi sector transfer fixup on read
+
+ntfs_attr_mst_pread() ignored the value returned by the multi sector
+transfer fixup. The fixup can fail in two ways. If the record cannot be
+protected at all because the update sequence array does not fit into the
+first sector or its size does not match the record it returns EINVAL and
+leaves the record untouched. If an incomplete transfer is found it
+returns EIO and sets the magic to BAAD.
+
+The second case is noticed later on because callers check the magic.
+The first case was not noticed at all so the record was used with the
+sector tails still holding the update sequence number instead of the
+real data and the size check in the fixup had no effect at all. Among
+other things this allowed records larger than an update sequence array
+can ever cover.
+
+diff --git a/libntfs-3g/attrib.c b/libntfs-3g/attrib.c
+index 35da32e8..85b3ada9 100644
+--- a/libntfs-3g/attrib.c
++++ b/libntfs-3g/attrib.c
+@@ -2673,8 +2673,17 @@ s64 ntfs_attr_mst_pread(ntfs_attr *na, const s64 pos, 
const s64 bk_cnt,
+               /* log errors unless silenced */
+       warn = !na->ni || !na->ni->vol || !NVolNoFixupWarn(na->ni->vol);
+       for (end = (u8*)dst + br * bk_size; (u8*)dst < end; dst = (u8*)dst +
+-                      bk_size)
+-              ntfs_mst_post_read_fixup_warn((NTFS_RECORD*)dst, bk_size, warn);
++                      bk_size) {
++              /*
++               * A record which cannot be multi sector transfer protected at
++               * all (EINVAL) has not been deprotected, so it must not be
++               * used. A record damaged by an incomplete transfer (EIO) is
++               * left to the caller, which detects it by the BAAD magic.
++               */
++              if (ntfs_mst_post_read_fixup_warn((NTFS_RECORD*)dst, bk_size,
++                                      warn) && (errno == EINVAL))
++                      return -1;
++      }
+       /* Finally, return the number of blocks read. */
+       return br;
+ }
diff -Nru ntfs-3g-2022.10.3/debian/patches/GHSA-mc3c-983p-wqm8-ef9b6d55.patch 
ntfs-3g-2022.10.3/debian/patches/GHSA-mc3c-983p-wqm8-ef9b6d55.patch
--- ntfs-3g-2022.10.3/debian/patches/GHSA-mc3c-983p-wqm8-ef9b6d55.patch 
1969-12-31 18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/GHSA-mc3c-983p-wqm8-ef9b6d55.patch 
2026-11-01 16:08:25.000000000 -0600
@@ -0,0 +1,49 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/ef9b6d5531741b8893aa685672168bceb9c50803
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+From ef9b6d5531741b8893aa685672168bceb9c50803 Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Thu, 20 Aug 2026 13:15:20 +0300
+Subject: [PATCH] Widen the runlist position counter.
+
+ntfs_mapping_pairs_decompress_i() keeps the index of the runlist element
+it is filling in a 16 bit variable. A mapping pair takes at least two
+bytes so an attribute holding more than 65535 runs makes that inde wrap
+around to zero. The check which grows the runlist buffer uses the same
+index so it stops growing the buffer as well and the elements decoded so
+far are overwritten one by one. The resulting runlist maps the wrong
+clusters.
+
+The writes stay within the buffer so this is a mapping error rather than
+a memory error. It also needs a mapping pairs array of 128 KiB which in
+turn needs an MFT record of 256 KiB far larger than any real volume
+uses.
+
+Make the index an int matching the buffer size variable next to it.
+Compare the number of elements needed against the buffer size instead
+of multiplying so that the comparison cannot overflow now that the
+index is no longer limited to 16 bits.
+
+diff --git a/libntfs-3g/runlist.c b/libntfs-3g/runlist.c
+index cb01e5a7..9793f88d 100644
+--- a/libntfs-3g/runlist.c
++++ b/libntfs-3g/runlist.c
+@@ -790,7 +790,7 @@ static runlist_element 
*ntfs_mapping_pairs_decompress_i(const ntfs_volume *vol,
+       const u8 *buf;          /* Current position in mapping pairs array. */
+       const u8 *attr_end;     /* End of attribute. */
+       int err, rlsize;        /* Size of runlist buffer. */
+-      u16 rlpos;              /* Current runlist position in units of
++      int rlpos;              /* Current runlist position in units of
+                                  runlist_elements. */
+       u8 b;                   /* Current byte offset in buf. */
+ 
+@@ -832,7 +832,7 @@ static runlist_element 
*ntfs_mapping_pairs_decompress_i(const ntfs_volume *vol,
+                * Allocate more memory if needed, including space for the
+                * not-mapped and terminator elements.
+                */
+-              if ((int)((rlpos + 3) * sizeof(*old_rl)) > rlsize) {
++              if ((rlpos + 3) > (rlsize / (int)sizeof(*old_rl))) {
+                       runlist_element *rl2;
+ 
+                       rlsize += 0x1000;
diff -Nru ntfs-3g-2022.10.3/debian/patches/GHSA-pc48-m7cx-qf72-5c588a6f.patch 
ntfs-3g-2022.10.3/debian/patches/GHSA-pc48-m7cx-qf72-5c588a6f.patch
--- ntfs-3g-2022.10.3/debian/patches/GHSA-pc48-m7cx-qf72-5c588a6f.patch 
1969-12-31 18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/GHSA-pc48-m7cx-qf72-5c588a6f.patch 
2026-11-01 16:08:25.000000000 -0600
@@ -0,0 +1,64 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/5c588a6f78e94bd84fdf3cb6bb0fcfb1c09c20d7
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From 5c588a6f78e94bd84fdf3cb6bb0fcfb1c09c20d7 Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Thu, 20 Aug 2026 20:42:40 +0300
+Subject: [PATCH] Do not read an ACE from an empty ACL.
+
+ntfs_acl_owner() walks the ACEs of the DACL with a do while loop so the
+body runs once before the count is tested. That is safe when the ACL
+holds at least one ACE because valid_acl() has bounded every one of
+them. It is not safe when ace_count is zero. In that case the loop in
+valid_acl() has no ACE to walk so it checks nothing and
+ntfs_valid_descr() only promises that the ACL header itself fits. So an
+empty ACL whose header ends at the end of the descriptor is accepted and
+the first ACE is then read past the end of the allocation.
+
+The read is small and its result only decides whether some ACE granted
+the right to change the owner. We cannot show that the bytes read can be
+recovered by an attacker because every value they are compared against
+is a constant. Two effects are real though. The read can fault when the
+next page is not mapped which brings down the mount. And if the bytes
+happen to look like an allow ACE carrying a user SID then a pointer
+outside the buffer is returned as the owner SID and is used to compute
+the permissions.
+
+An empty ACL is also a normal thing to meet on a volume because it means
+deny everyone. When the DACL is not the last field the stray read stays
+inside the descriptor and nothing faults but a wrong owner can still
+come out of it. So this is not only reachable with a crafted volume.
+Test the count before reading so an empty ACL reads nothing.
+
+diff --git a/libntfs-3g/acls.c b/libntfs-3g/acls.c
+index 9f16fecd0..ff5f723db 100644
+--- a/libntfs-3g/acls.c
++++ b/libntfs-3g/acls.c
+@@ -3581,14 +3581,22 @@ const SID *ntfs_acl_owner(const char *securattr)
+               acecnt = le16_to_cpu(pacl->ace_count);
+               offace = offdacl + sizeof(ACL);
+               nace = 0;
+-              do {
++                      /*
++                       * Test the count before reading an ACE. An empty ACL
++                       * has none to read and the bytes which would hold the
++                       * first one are not part of the ACL. valid_acl() has
++                       * not checked them either because it had no ACE to
++                       * walk.
++                       */
++              while (!found && (nace < acecnt)) {
+                       pace = (const ACCESS_ALLOWED_ACE*)&securattr[offace];
+                       if ((pace->mask & WRITE_OWNER)
+                          && (pace->type == ACCESS_ALLOWED_ACE_TYPE)
+                          && ntfs_is_user_sid(&pace->sid))
+                               found = TRUE;
+                       offace += le16_to_cpu(pace->size);
+-              } while (!found && (++nace < acecnt));
++                      nace++;
++              }
+       }
+       if (found)
+               usid = &pace->sid;
diff -Nru ntfs-3g-2022.10.3/debian/patches/GHSA-pc48-m7cx-qf72-660a79c8.patch 
ntfs-3g-2022.10.3/debian/patches/GHSA-pc48-m7cx-qf72-660a79c8.patch
--- ntfs-3g-2022.10.3/debian/patches/GHSA-pc48-m7cx-qf72-660a79c8.patch 
1969-12-31 18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/GHSA-pc48-m7cx-qf72-660a79c8.patch 
2026-11-01 16:08:25.000000000 -0600
@@ -0,0 +1,149 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/660a79c8fa746aebdca5b2c671682321c98c7513
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+From 660a79c8fa746aebdca5b2c671682321c98c7513 Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Thu, 20 Aug 2026 20:08:44 +0300
+Subject: [PATCH] Do not read an ACE SID which is not at the standard offset.
+
+valid_acl() checks the SID of an ACE only for the types it knows. For
+the others it just checks that the ACE fits and leaves the SID alone
+with the comment that the SID is at a different location. That is
+accurate. The object types first keep a flag word and up to two GUIDs so
+their SID does not start at the usual offset. Worse the flags tell which
+of the GUIDs are present so the SID may begin at 12 or 28 or 44 and
+cannot be located from the ACE type alone. MS-DTYP section 2.4.4.1 lists
+every ACE type and the description of ACCESS_ALLOWED_OBJECT_ACE states
+that the offset of SidStart can vary.
+
+The permission builders read a SID at the standard offset for every ACE
+whatever the type. Two things follow. They compare GUID bytes against
+real SIDs which is meaningless. And because nothing has bounded those
+bytes an ACE which claims fifteen sub authorities makes ntfs_same_sid()
+compare sixty eight bytes which can reach past the end of the
+descriptor. This is reachable when the permissions of a file on a
+crafted volume are computed.
+
+Add a helper which tells whether the ACE type keeps its SID at the
+standard offset and list there the types of MS-DTYP section 2.4.4.1
+which are neither an object type nor reserved. Skip the other ACEs in
+the three builders and in the Posix variant. Nothing is lost by skipping
+them because a contribution to the permissions needs an allow or a deny
+type and both of those keep the SID where it is expected.
+
+References :
+https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/628ebb1d-c509-4ea0-a10f-77ef97ca4586
+https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-access_allowed_object_ace
+
+diff --git a/libntfs-3g/acls.c b/libntfs-3g/acls.c
+index de072962..f7d6d5b0 100644
+--- a/libntfs-3g/acls.c
++++ b/libntfs-3g/acls.c
+@@ -597,6 +597,59 @@ static BOOL valid_acl(const ACL *pacl, unsigned int end)
+       return (ok);
+ }
+ 
++/**
++ * sid_at_standard_offset - check whether an ACE keeps its SID where expected
++ * @pace:     the ACE to check
++ *
++ * Most ACE types keep the SID just after the header and the access mask :
++ *
++ *      | header | mask |              SID              |
++ *      |========|======|===============================|
++ *      0        4      8
++ *
++ * The object types first keep a flag word and up to two GUIDs. The flags tell
++ * which of the GUIDs are present so the SID may begin at 12 or 28 or 44 :
++ *
++ *      | header | mask | flags | object type | inherited object type | SID |
++ *      |========|======|=======|=============|=======================|=====|
++ *      0        4      8      12            28                      44
++ *
++ * So for an object type the SID cannot even be located from the type alone.
++ *
++ * The types listed below are the ones whose SID valid_acl() has checked, so
++ * for them the SID is known to lie within the ACE. The others either move the
++ * SID as shown above or are reserved with no defined layout, and for those 
the
++ * bytes at the standard offset are not a SID and must not be read as one.
++ *
++ * The list is the set of types in MS-DTYP section 2.4.4.1 which are neither 
an
++ * object type nor reserved. It has to be kept in step with valid_acl().
++ *
++ * Return TRUE if the SID is at the standard offset and FALSE otherwise.
++ */
++
++static BOOL sid_at_standard_offset(const ACCESS_ALLOWED_ACE *pace)
++{
++      BOOL standard;
++
++      switch (pace->type) {
++      case ACCESS_ALLOWED_ACE_TYPE :
++      case ACCESS_DENIED_ACE_TYPE :
++      case SYSTEM_AUDIT_ACE_TYPE :
++      case ACCESS_ALLOWED_CALLBACK_ACE_TYPE :
++      case ACCESS_DENIED_CALLBACK_ACE_TYPE :
++      case SYSTEM_AUDIT_CALLBACK_ACE_TYPE :
++      case SYSTEM_MANDATORY_LABEL_ACE_TYPE :
++      case SYSTEM_RESOURCE_ATTRIBUTE_ACE_TYPE :
++      case SYSTEM_SCOPED_POLICY_ID_ACE_TYPE :
++              standard = TRUE;
++              break;
++      default :
++              standard = FALSE;
++              break;
++      }
++      return (standard);
++}
++
+ /*
+  *            Do sanity checks on security descriptors read from storage
+  *    basically, we make sure that every field holds within
+@@ -3342,7 +3395,8 @@ static int build_std_permissions(const char *securattr,
+       }
+       for (nace = 0; nace < acecnt; nace++) {
+               pace = (const ACCESS_ALLOWED_ACE*)&securattr[offace];
+-              if (!(pace->flags & INHERIT_ONLY_ACE)) {
++              if (!(pace->flags & INHERIT_ONLY_ACE)
++                 && sid_at_standard_offset(pace)) {
+                       if (ntfs_same_sid(usid, &pace->sid)
+                         || ntfs_same_sid(ownersid, &pace->sid)) {
+                               noown = FALSE;
+@@ -3432,7 +3486,8 @@ static int build_owngrp_permissions(const char 
*securattr,
+       }
+       for (nace = 0; nace < acecnt; nace++) {
+               pace = (const ACCESS_ALLOWED_ACE*)&securattr[offace];
+-              if (!(pace->flags & INHERIT_ONLY_ACE)) {
++              if (!(pace->flags & INHERIT_ONLY_ACE)
++                 && sid_at_standard_offset(pace)) {
+                       if ((ntfs_same_sid(usid, &pace->sid)
+                          || ntfs_same_sid(ownersid, &pace->sid))
+                           && (pace->mask & WRITE_OWNER)) {
+@@ -3623,7 +3678,8 @@ static int build_ownadmin_permissions(const char 
*securattr,
+       for (nace = 0; nace < acecnt; nace++) {
+               pace = (const ACCESS_ALLOWED_ACE*)&securattr[offace];
+               if (!(pace->flags & INHERIT_ONLY_ACE)
+-                 && !(~pace->mask & (ROOT_OWNER_UNMARK | ROOT_GROUP_UNMARK))) 
{
++                 && !(~pace->mask & (ROOT_OWNER_UNMARK | ROOT_GROUP_UNMARK))
++                 && sid_at_standard_offset(pace)) {
+                       if ((ntfs_same_sid(usid, &pace->sid)
+                          || ntfs_same_sid(ownersid, &pace->sid))
+                            && (((pace->mask & WRITE_OWNER) && firstapply))) {
+@@ -3893,6 +3949,16 @@ struct POSIX_SECURITY *ntfs_build_permissions_posix(
+                       pctx = &ctx[0];
+               }
+               ignore = FALSE;
++                      /*
++                       * Only look for a SID at the standard offset when the
++                       * ACE type keeps one there. An ACE which does not
++                       * could never contribute to the Posix ACL below, as
++                       * that needs an allow or deny type.
++                       */
++              if (!sid_at_standard_offset(pace)) {
++                      offace += le16_to_cpu(pace->size);
++                      continue;
++              }
+                       /*
+                        * grants for root as a designated user or group
+                        */
diff -Nru ntfs-3g-2022.10.3/debian/patches/GHSA-r6xj-6488-p8mv.patch 
ntfs-3g-2022.10.3/debian/patches/GHSA-r6xj-6488-p8mv.patch
--- ntfs-3g-2022.10.3/debian/patches/GHSA-r6xj-6488-p8mv.patch  1969-12-31 
18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/GHSA-r6xj-6488-p8mv.patch  2026-11-01 
16:08:25.000000000 -0600
@@ -0,0 +1,51 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/d78b8a99241766802dd0ac1ed004e9fb2298d6a8
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From d78b8a99241766802dd0ac1ed004e9fb2298d6a8 Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Thu, 20 Aug 2026 11:25:57 +0300
+Subject: [PATCH] index.c: reject an index entry too long to hold a VCN
+
+ntfs_ie_add_vcn() adds eight bytes to the length of an index entry so
+that a VCN can be stored at the end of it. The length is a 16 bit field,
+so when the entry is already 0xfff8 bytes long the new length wraps
+around to zero.
+
+The entry is then passed to realloc() with a size of zero, and what
+follows depends on the C library. It may free the entry and return NULL,
+in which case the function reports an error while the caller still holds
+the old pointer and frees it a second time. It may instead return a
+minimal allocation, in which case the VCN is written eight bytes before
+the start of it. So either a double free or an out of bounds write is
+possible.
+
+diff --git a/libntfs-3g/index.c b/libntfs-3g/index.c
+index e48d6aafb..c2cdf23a7 100644
+--- a/libntfs-3g/index.c
++++ b/libntfs-3g/index.c
+@@ -1379,12 +1379,19 @@ static int ntfs_ir_make_space(ntfs_index_context *icx, 
int data_size)
+ static int ntfs_ie_add_vcn(INDEX_ENTRY **ie)
+ {
+       INDEX_ENTRY *p, *old = *ie;
+-       
+-      old->length = cpu_to_le16(le16_to_cpu(old->length) + sizeof(VCN));
+-      p = realloc(old, le16_to_cpu(old->length));
++      u32 new_length = le16_to_cpu(old->length) + (u32)sizeof(VCN);
++
++      if (new_length > 0xffff) {
++              ntfs_log_error("Index entry of length %u cannot hold a VCN\n",
++                              (unsigned)le16_to_cpu(old->length));
++              errno = EIO;
++              return STATUS_ERROR;
++      }
++      p = realloc(old, new_length);
+       if (!p)
+               return STATUS_ERROR;
+       
++      p->length = cpu_to_le16(new_length);
+       p->ie_flags |= INDEX_ENTRY_NODE;
+       *ie = p;
+ 
diff -Nru ntfs-3g-2022.10.3/debian/patches/GHSA-wf3w-fjjg-x4w3.patch 
ntfs-3g-2022.10.3/debian/patches/GHSA-wf3w-fjjg-x4w3.patch
--- ntfs-3g-2022.10.3/debian/patches/GHSA-wf3w-fjjg-x4w3.patch  1969-12-31 
18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/GHSA-wf3w-fjjg-x4w3.patch  2026-11-01 
16:08:25.000000000 -0600
@@ -0,0 +1,46 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/194eaa9cb4f0f6f9aa24b61019e141f531cbc116
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From 194eaa9cb4f0f6f9aa24b61019e141f531cbc116 Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Thu, 20 Aug 2026 14:05:06 +0300
+Subject: [PATCH] Bounds check the attribute name in ntfs_external_attr_find().
+
+ntfs_external_attr_find() compares the attribute name of an extent
+record without checking that the name lies within the attribute. A
+crafted name offset makes ntfs_names_are_equal() read up to 510 bytes
+past the attribute.
+
+The same check is done in ntfs_attr_inconsistent() when the mft record
+is read but that validation is skipped when NVolNoFixupWarn() is set
+which ntfsclone and ntfswipe do. Both open inodes and look up attributes
+so they can reach this compare with an unchecked name offset.
+
+diff --git a/libntfs-3g/attrib.c b/libntfs-3g/attrib.c
+index efb919438..4570f6820 100644
+--- a/libntfs-3g/attrib.c
++++ b/libntfs-3g/attrib.c
+@@ -3338,6 +3338,20 @@ do_next_attr_loop:
+                */
+               if (al_entry->type != a->type)
+                       break;
++              /*
++               * The same check is made in ntfs_attr_inconsistent() when the
++               * mft record is read, but that validation is skipped for the
++               * tools which set NVolNoFixupWarn(), so the name has to be
++               * checked here as well.
++               */
++              if (a->name_length && ((le16_to_cpu(a->name_offset)
++                              + a->name_length * sizeof(ntfschar))
++                              > le32_to_cpu(a->length))) {
++                      ntfs_log_error("Corrupt attribute name"
++                              " in MFT record %lld\n",
++                              (long long)ctx->ntfs_ino->mft_no);
++                      break;
++              }
+               if (!ntfs_names_are_equal((ntfschar*)((char*)a +
+                               le16_to_cpu(a->name_offset)),
+                               a->name_length, al_name,
diff -Nru ntfs-3g-2022.10.3/debian/patches/GHSA-x98j-3g35-f59x.patch 
ntfs-3g-2022.10.3/debian/patches/GHSA-x98j-3g35-f59x.patch
--- ntfs-3g-2022.10.3/debian/patches/GHSA-x98j-3g35-f59x.patch  1969-12-31 
18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/GHSA-x98j-3g35-f59x.patch  2026-11-01 
16:08:25.000000000 -0600
@@ -0,0 +1,69 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/1144da0ac50483ee180190b1984ed54269aa31e8
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+From 1144da0ac50483ee180190b1984ed54269aa31e8 Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Thu, 20 Aug 2026 19:50:20 +0300
+Subject: [PATCH] Bound the owner / group SIDs and the ACL sizes in a security
+ descriptor.
+
+ntfs_valid_descr() is meant to guarantee that every field of a security
+descriptor lies within the allocated storage but it did not check the
+extent of the owner SID the group SID or the two ACLs. The only bound on
+the owner SID was that its first two bytes are readable and the rest was
+left to ntfs_attr_size() which folds a field into its total only when
+the field lies beyond the ones examined before it.
+
+The owner SID is normally placed before the group SID so its extent was
+usually not accounted for at all. A descriptor claiming an owner SID of
+fifteen sub authorities could therefore be accepted with that SID ending
+past the buffer. The same applies to a DACL or SACL placed below the end
+of the group SID whose size field was never checked.
+
+feedsecurityattr() then takes the size of the owner SID from the
+descriptor and copies that many bytes into the buffer it returns to the
+caller. Auditing a crafted volume with ntfssecaudit copied up to sixty
+bytes from beyond the allocation into the audited output and those bytes
+included heap addresses. Building the unix permissions could also read
+past the end while comparing SIDs.
+
+Check the four extents directly instead. This is done after the SID
+revision and sub authority count have been validated so the sizes are
+sane and after the alignment checks so the offsets cannot wrap.
+
+diff --git a/libntfs-3g/acls.c b/libntfs-3g/acls.c
+index 03109c55..de072962 100644
+--- a/libntfs-3g/acls.c
++++ b/libntfs-3g/acls.c
+@@ -658,6 +658,30 @@ BOOL ntfs_valid_descr(const char *securattr, unsigned int 
attrsz)
+               && (ntfs_attr_size(securattr) <= attrsz)
+               && ntfs_valid_sid((const SID*)&securattr[offowner])
+               && ntfs_valid_sid((const SID*)&securattr[offgroup])
++                      /*
++                       * Check that the owner and group SIDs, and the ACLs,
++                       * end within the allocated storage.
++                       *
++                       * ntfs_attr_size() cannot be relied on for this. It
++                       * only takes a field into account when the field lies
++                       * beyond the ones examined before it, so a field at a
++                       * lower offset is left out of the size it returns. The
++                       * owner SID is the usual case, as it is placed before
++                       * the group SID in most descriptors.
++                       *
++                       * The SID sizes are known sane here because
++                       * ntfs_valid_sid() has just bounded the sub authority
++                       * count, and the ACL sizes are readable because the
++                       * ACL headers were checked to be within storage.
++                       */
++              && ((offowner + (unsigned int)ntfs_sid_size((const SID*)
++                              &securattr[offowner])) <= attrsz)
++              && ((offgroup + (unsigned int)ntfs_sid_size((const SID*)
++                              &securattr[offgroup])) <= attrsz)
++              && (!offdacl
++                              || ((offdacl + le16_to_cpu(pdacl->size)) <= 
attrsz))
++              && (!offsacl
++                              || ((offsacl + le16_to_cpu(psacl->size)) <= 
attrsz))
+                       /*
+                        * if there is an ACL, as indicated by offdacl,
+                        * require SE_DACL_PRESENT
diff -Nru ntfs-3g-2022.10.3/debian/patches/GHSA-xrvx-6jrp-4q3x.patch 
ntfs-3g-2022.10.3/debian/patches/GHSA-xrvx-6jrp-4q3x.patch
--- ntfs-3g-2022.10.3/debian/patches/GHSA-xrvx-6jrp-4q3x.patch  1969-12-31 
18:00:00.000000000 -0600
+++ ntfs-3g-2022.10.3/debian/patches/GHSA-xrvx-6jrp-4q3x.patch  2026-11-01 
16:08:25.000000000 -0600
@@ -0,0 +1,56 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/6817909126e5770fa86c2c4c8524632fe29c08c6
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From 6817909126e5770fa86c2c4c8524632fe29c08c6 Mon Sep 17 00:00:00 2001
+From: Rakesh Pandit <[email protected]>
+Date: Thu, 20 Aug 2026 15:50:47 +0300
+Subject: [PATCH] Bound the restart area offset by the fixed RESTART_AREA size.
+
+ntfs_check_restart_area() only checked that the fields before
+ra->file_size were ahead of the first word protected by an update
+sequence number. It then went on to read file_size itself and the fields
+after it.
+
+With the largest offset the checks allow 480 offset,
+log_record_header_length and log_page_data_offset are read at 516 and
+518 offsets resp. which is past the 512 byte buffer holding the first
+block of the restart page. file_size ends at 511 so its last two bytes
+are the update sequence number rather than the stored size because the
+fixups have not been applied at this point.
+
+Bound the offset by the size of the fixed RESTART_AREA instead so that
+every field read here stays below the sector word. Real restart areas
+are followed by the client array at 0x30 or 0x40 so the existing
+client_array_offset check already implies this bound for them.
+
+diff --git a/libntfs-3g/logfile.c b/libntfs-3g/logfile.c
+index 9c3155e78..e5e8af63c 100644
+--- a/libntfs-3g/logfile.c
++++ b/libntfs-3g/logfile.c
+@@ -177,14 +177,16 @@ static BOOL ntfs_check_restart_area(RESTART_PAGE_HEADER 
*rp)
+       ra_ofs = le16_to_cpu(rp->restart_area_offset);
+       ra = (RESTART_AREA*)((u8*)rp + ra_ofs);
+       /*
+-       * Everything before ra->file_size must be before the first word
+-       * protected by an update sequence number.  This ensures that it is
+-       * safe to access ra->client_array_offset.
++       * The fixed RESTART_AREA prefix must be entirely before the first
++       * MST-protected sector word.  Fixups have not been applied yet, so this
++       * makes every fixed field accessed below safe to read.
++       *
++       * The subsequent client_array_offset check bounds any format-specific
++       * extension before the client array.
+        */
+-      if (ra_ofs + offsetof(RESTART_AREA, file_size) >
+-                      NTFS_BLOCK_SIZE - sizeof(u16)) {
+-              ntfs_log_error("$LogFile restart area specifies "
+-                              "inconsistent file offset.\n");
++      if (ra_ofs + sizeof(RESTART_AREA) > NTFS_BLOCK_SIZE - sizeof(u16)) {
++              ntfs_log_error("$LogFile restart area extends into the first "
++                              "MST-protected sector word.\n");
+               return FALSE;
+       }
+       /*
diff -Nru 
ntfs-3g-2022.10.3/debian/patches/Revert-attrib.c-check-the-multi-sector-transfer-fixu.patch
 
ntfs-3g-2022.10.3/debian/patches/Revert-attrib.c-check-the-multi-sector-transfer-fixu.patch
--- 
ntfs-3g-2022.10.3/debian/patches/Revert-attrib.c-check-the-multi-sector-transfer-fixu.patch
 1969-12-31 18:00:00.000000000 -0600
+++ 
ntfs-3g-2022.10.3/debian/patches/Revert-attrib.c-check-the-multi-sector-transfer-fixu.patch
 2026-11-01 16:08:25.000000000 -0600
@@ -0,0 +1,45 @@
+Origin: 
https://github.com/tuxera/ntfs-3g/commit/9398030d978b064c62f304b1a514c7a494d37b62
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-11-01
+
+Backported to 2022.10.3.
+
+From 9398030d978b064c62f304b1a514c7a494d37b62 Mon Sep 17 00:00:00 2001
+From: Erik Larsson <[email protected]>
+Date: Mon, 28 Sep 2026 12:05:41 +0300
+Subject: [PATCH] Revert "attrib.c: check the multi sector transfer fixup on
+ read"
+
+This is done in response to a regression from version 2026.7.7 reported
+in github issue #217, reported by @amehn77, and in issue #218, opened by
+@xcdmrCHP.
+
+This reverts commit 056ce3a90d995894bfd4ce282be7b66ebb20e7ac.
+
+diff --git a/libntfs-3g/attrib.c b/libntfs-3g/attrib.c
+index 3f12274708ef..87b30f950b58 100644
+--- a/libntfs-3g/attrib.c
++++ b/libntfs-3g/attrib.c
+@@ -2673,17 +2673,8 @@ s64 ntfs_attr_mst_pread(ntfs_attr *na, const s64 pos, 
const s64 bk_cnt,
+               /* log errors unless silenced */
+       warn = !na->ni || !na->ni->vol || !NVolNoFixupWarn(na->ni->vol);
+       for (end = (u8*)dst + br * bk_size; (u8*)dst < end; dst = (u8*)dst +
+-                      bk_size) {
+-              /*
+-               * A record which cannot be multi sector transfer protected at
+-               * all (EINVAL) has not been deprotected, so it must not be
+-               * used. A record damaged by an incomplete transfer (EIO) is
+-               * left to the caller, which detects it by the BAAD magic.
+-               */
+-              if (ntfs_mst_post_read_fixup_warn((NTFS_RECORD*)dst, bk_size,
+-                                      warn) && (errno == EINVAL))
+-                      return -1;
+-      }
++                      bk_size)
++              ntfs_mst_post_read_fixup_warn((NTFS_RECORD*)dst, bk_size, warn);
+       /* Finally, return the number of blocks read. */
+       return br;
+ }
+-- 
+2.55.0
+
diff -Nru ntfs-3g-2022.10.3/debian/patches/series 
ntfs-3g-2022.10.3/debian/patches/series
--- ntfs-3g-2022.10.3/debian/patches/series     2026-04-16 16:26:49.000000000 
-0500
+++ ntfs-3g-2022.10.3/debian/patches/series     2026-11-01 15:57:03.000000000 
-0600
@@ -1,2 +1,25 @@
 0001-Fix_use-after-free_in_ntfs_uppercase_mbs.patch
 ntfs-3g_2022.10.3-cve_2026-02.patch
+CVE-2026-42618-e021cbe3.patch
+CVE-2026-42618-6aa66d98.patch
+CVE-2026-42616.patch
+CVE-2026-42617.patch
+CVE-2026-46572.patch
+CVE-2026-56136.patch
+CVE-2026-46569.patch
+CVE-2026-46571.patch
+CVE-2026-46570.patch
+CVE-2026-56135-289ebcf3.patch
+CVE-2026-56135-21f37bb5.patch
+CVE-2026-56135-b0b9c32c.patch
+GHSA-r6xj-6488-p8mv.patch
+GHSA-mc3c-983p-wqm8-ef9b6d55.patch
+GHSA-mc3c-983p-wqm8-056ce3a9.patch
+GHSA-wf3w-fjjg-x4w3.patch
+GHSA-2c97-47cr-9xr8.patch
+GHSA-xrvx-6jrp-4q3x.patch
+GHSA-jcjj-9262-6j6p.patch
+GHSA-x98j-3g35-f59x.patch
+GHSA-pc48-m7cx-qf72-660a79c8.patch
+GHSA-pc48-m7cx-qf72-5c588a6f.patch
+Revert-attrib.c-check-the-multi-sector-transfer-fixu.patch

Attachment: OpenPGP_signature.asc
Description: OpenPGP digital signature

Reply via email to