Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package ntfs-3g_ntfsprogs for openSUSE:Factory checked in at 2026-07-21 22:54:52 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ntfs-3g_ntfsprogs (Old) and /work/SRC/openSUSE:Factory/.ntfs-3g_ntfsprogs.new.24530 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ntfs-3g_ntfsprogs" Tue Jul 21 22:54:52 2026 rev:36 rq:1366709 version:2022.10.3 Changes: -------- --- /work/SRC/openSUSE:Factory/ntfs-3g_ntfsprogs/ntfs-3g_ntfsprogs.changes 2026-05-13 20:59:03.176435333 +0200 +++ /work/SRC/openSUSE:Factory/.ntfs-3g_ntfsprogs.new.24530/ntfs-3g_ntfsprogs.changes 2026-07-21 22:54:53.662414748 +0200 @@ -1,0 +2,17 @@ +Tue Jul 14 14:26:30 UTC 2026 - Scott Bradnick <[email protected]> + +- Adding patch bundle from upstream (download.tuxera.com/opensource) + re:cve_2026-04 for v2022.10.3 + * bsc#1271104 -> 1_ntfs-3g_2022.10.3-CVE-2026-42618.patch + * bsc#1271102 -> 2_ntfs-3g_2022.10.3-CVE-2026-42616.patch + * bsc#1271103 -> 3_ntfs-3g_2022.10.3-CVE-2026-42617.patch + * bsc#1271105 -> 4_ntfs-3g_2022.10.3-CVE-2026-46569.patch + * bsc#1271107 -> 5_ntfs-3g_2022.10.3-CVE-2026-46571.patch + * bsc#1271106 -> 6_ntfs-3g_2022.10.3-CVE-2026-46570.patch + * bsc#1271108 -> 7_ntfs-3g_2022.10.3-CVE-2026-46572.patch + - This file is identical to 3_...42617.patch + * bsc#1271109 -> 8_ntfs-3g_2022.10.3-CVE-2026-56135.patch + * bsc#1271110 -> 9_ntfs-3g_2022.10.3-CVE-2026-56136.patch + - This file is identical to 3_...42617.patch + +------------------------------------------------------------------- New: ---- 1_ntfs-3g_2022.10.3-CVE-2026-42618.patch 2_ntfs-3g_2022.10.3-CVE-2026-42616.patch 3_ntfs-3g_2022.10.3-CVE-2026-42617.patch 4_ntfs-3g_2022.10.3-CVE-2026-46569.patch 5_ntfs-3g_2022.10.3-CVE-2026-46571.patch 6_ntfs-3g_2022.10.3-CVE-2026-46570.patch 8_ntfs-3g_2022.10.3-CVE-2026-56135.patch ----------(New B)---------- New: re:cve_2026-04 for v2022.10.3 * bsc#1271104 -> 1_ntfs-3g_2022.10.3-CVE-2026-42618.patch * bsc#1271102 -> 2_ntfs-3g_2022.10.3-CVE-2026-42616.patch New: * bsc#1271104 -> 1_ntfs-3g_2022.10.3-CVE-2026-42618.patch * bsc#1271102 -> 2_ntfs-3g_2022.10.3-CVE-2026-42616.patch * bsc#1271103 -> 3_ntfs-3g_2022.10.3-CVE-2026-42617.patch New: * bsc#1271102 -> 2_ntfs-3g_2022.10.3-CVE-2026-42616.patch * bsc#1271103 -> 3_ntfs-3g_2022.10.3-CVE-2026-42617.patch * bsc#1271105 -> 4_ntfs-3g_2022.10.3-CVE-2026-46569.patch New: * bsc#1271103 -> 3_ntfs-3g_2022.10.3-CVE-2026-42617.patch * bsc#1271105 -> 4_ntfs-3g_2022.10.3-CVE-2026-46569.patch * bsc#1271107 -> 5_ntfs-3g_2022.10.3-CVE-2026-46571.patch New: * bsc#1271105 -> 4_ntfs-3g_2022.10.3-CVE-2026-46569.patch * bsc#1271107 -> 5_ntfs-3g_2022.10.3-CVE-2026-46571.patch * bsc#1271106 -> 6_ntfs-3g_2022.10.3-CVE-2026-46570.patch New: * bsc#1271107 -> 5_ntfs-3g_2022.10.3-CVE-2026-46571.patch * bsc#1271106 -> 6_ntfs-3g_2022.10.3-CVE-2026-46570.patch * bsc#1271108 -> 7_ntfs-3g_2022.10.3-CVE-2026-46572.patch New: - This file is identical to 3_...42617.patch * bsc#1271109 -> 8_ntfs-3g_2022.10.3-CVE-2026-56135.patch * bsc#1271110 -> 9_ntfs-3g_2022.10.3-CVE-2026-56136.patch ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ntfs-3g_ntfsprogs.spec ++++++ --- /var/tmp/diff_new_pack.U21UU7/_old 2026-07-21 22:54:54.386439403 +0200 +++ /var/tmp/diff_new_pack.U21UU7/_new 2026-07-21 22:54:54.386439403 +0200 @@ -34,6 +34,17 @@ Patch0: ntfs3g-unistr-use-after-free.patch # PATCH-FIX-UPSTREAM ntfs3g-heap-overflow.patch bsc#1262216 [email protected] -- fix heap overflow Patch1: ntfs3g-heap-overflow.patch +Patch2: 1_ntfs-3g_2022.10.3-CVE-2026-42618.patch +Patch3: 2_ntfs-3g_2022.10.3-CVE-2026-42616.patch +Patch4: 3_ntfs-3g_2022.10.3-CVE-2026-42617.patch +Patch5: 4_ntfs-3g_2022.10.3-CVE-2026-46569.patch +Patch6: 5_ntfs-3g_2022.10.3-CVE-2026-46571.patch +Patch7: 6_ntfs-3g_2022.10.3-CVE-2026-46570.patch +# This is the same as 3_...42617.patch +#Patch8: 7_ntfs-3g_2022.10.3-CVE-2026-46572.patch +Patch9: 8_ntfs-3g_2022.10.3-CVE-2026-56135.patch +# This is the same as 3_...42617.patch +#Patch10: 9_ntfs-3g_2022.10.3-CVE-2026-56136.patch BuildRequires: gnutls-devel BuildRequires: hwinfo-devel BuildRequires: libgcrypt-devel ++++++ 1_ntfs-3g_2022.10.3-CVE-2026-42618.patch ++++++ diff --git a/libntfs-3g/compress.c b/libntfs-3g/compress.c index 390b2d97d..50d9daf32 100644 --- a/libntfs-3g/compress.c +++ b/libntfs-3g/compress.c @@ -459,7 +459,8 @@ static unsigned int ntfs_compress_block(const char *inbuf, const int bufsize, * @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 @@ static int ntfs_decompress(u8 *dest, const u32 dest_size, /* 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)); - /* - * 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 (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; - } + { + ntfs_log_debug("Beginning sub-block at offset = %d in the " + "cb.\n", + (int)(cb - cb_start)); /* - * 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. + * 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 (dest == dest_sb_start) + 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; - /* - * 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) + 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; - /* Now calculate the length of the byte sequence. */ - length = (pt & (0xfff >> lg)) + 3; - /* Verify destination is in range. */ - if (dest + length > dest_sb_end) + /* Does the minimum size of a compressed sb overflow valid + * range? */ + if ((size_t)(cb_end - cb) < 6) 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 { + /* 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; /* - * 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. + * 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. */ - 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++; + 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; } - /* 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; + } + /* + * If the output sub-block filled before the compressed sub-block input + * was exhausted, skip the remaining compressed bytes in this sub-block. + */ + 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"); ++++++ 2_ntfs-3g_2022.10.3-CVE-2026-42616.patch ++++++ 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) { ++++++ 3_ntfs-3g_2022.10.3-CVE-2026-42617.patch ++++++ diff --git a/include/ntfs-3g/index.h b/include/ntfs-3g/index.h index d001863ae..1d5845d47 100644 --- a/include/ntfs-3g/index.h +++ b/include/ntfs-3g/index.h @@ -143,6 +143,7 @@ extern int ntfs_index_block_inconsistent(const INDEX_BLOCK *ib, u32 block_size, 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/attrib.c b/libntfs-3g/attrib.c index efb919438..cfc2446e3 100644 --- a/libntfs-3g/attrib.c +++ b/libntfs-3g/attrib.c @@ -3528,6 +3528,10 @@ int ntfs_attr_inconsistent(const ATTR_RECORD *a, const MFT_REF mref) 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) @@ -3542,6 +3546,9 @@ int ntfs_attr_inconsistent(const ATTR_RECORD *a, const MFT_REF mref) (long long)inum); errno = EIO; ret = -1; + } else if (ntfs_ie_stream_inconsistent(&ir->index, inum)) { + errno = EIO; + ret = -1; } break; case AT_STANDARD_INFORMATION : diff --git a/libntfs-3g/index.c b/libntfs-3g/index.c index e48d6aafb..c9651a62c 100644 --- a/libntfs-3g/index.c +++ b/libntfs-3g/index.c @@ -504,6 +504,8 @@ int ntfs_index_block_inconsistent(const INDEX_BLOCK *ib, u32 block_size, (unsigned long long)inum); return -1; } + if (ntfs_ie_stream_inconsistent(&ib->index, inum)) + return -1; return (0); } @@ -560,7 +562,40 @@ int ntfs_index_entry_inconsistent(const INDEX_ENTRY *ie, 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: @@ -1091,14 +1126,16 @@ out: 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 +1147,18 @@ static INDEX_BLOCK *ntfs_ir_to_ib(INDEX_ROOT *ir, VCN ib_vcn) * 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; ++++++ 4_ntfs-3g_2022.10.3-CVE-2026-46569.patch ++++++ 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 + ++++++ 5_ntfs-3g_2022.10.3-CVE-2026-46571.patch ++++++ 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]; + } } } } ++++++ 6_ntfs-3g_2022.10.3-CVE-2026-46570.patch ++++++ 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; ++++++ 8_ntfs-3g_2022.10.3-CVE-2026-56135.patch ++++++ diff --git a/include/ntfs-3g/acls.h b/include/ntfs-3g/acls.h index 932791e97..4f36389c5 100644 --- a/include/ntfs-3g/acls.h +++ b/include/ntfs-3g/acls.h @@ -168,6 +168,8 @@ char *ntfs_build_descr_posix(struct MAPPING* const mapping[], #endif /* POSIXACLS */ +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 9f16fecd0..f9ed58f40 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; @@ -681,6 +683,87 @@ BOOL ntfs_valid_descr(const char *securattr, unsigned int attrsz) return (ok); } +/** + * 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, 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: extra bytes needed, or 0 if @acl is NULL or structurally rejected. + */ + +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 extra; + BOOL usid_is_group_sid; + + 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) + break; + ace = (const ACCESS_ALLOWED_ACE*)((const char*)acl + off); + acesz = le16_to_cpu(ace->size); + if (acesz < 8 || acesz > acl_size - off) + break; + 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 extra; +} + /* * Copy the inheritable parts of an ACL * diff --git a/libntfs-3g/security.c b/libntfs-3g/security.c index acee0a5ed..d0e030e19 100644 --- a/libntfs-3g/security.c +++ b/libntfs-3g/security.c @@ -3944,6 +3944,30 @@ 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 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_inherit_acl_extra_size(ppacl, + usid, gsid); + } + } + 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_inherit_acl_extra_size(ppacl, + usid, gsid); + } + } if (fordir) newattrsz *= 2; newattr = (char*)ntfs_malloc(newattrsz);
