]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
fs/ntfs3: add bounds check to run_get_highest_vcn()
authorKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Thu, 30 Apr 2026 12:30:13 +0000 (14:30 +0200)
committerKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Tue, 2 Jun 2026 15:02:23 +0000 (17:02 +0200)
run_get_highest_vcn() parses a packed NTFS mapping-pairs buffer without
any length bound, relying solely on a 0x00 terminator to stop.  A
crafted $LogFile UpdateMappingPairs record whose embedded attribute
contains mapping-pairs runs without a terminator causes the function to
read past the slab allocation, triggering a KASAN slab-out-of-bounds
read on mount.

The sibling function run_unpack() received an analogous bounds-check in
commit b62567bca474 ("ntfs3: add buffer boundary checks to run_unpack()"),
but run_get_highest_vcn() was missed.

Take a run_buf_size parameter and reject any run header whose payload
would extend past the buffer end, mirroring the pattern used by
run_unpack().  The caller in fslog.c passes the remaining attribute
bytes after the mapping-pairs offset.

KASAN report (on mainline v7.1 merge window HEAD):

  BUG: KASAN: slab-out-of-bounds in run_get_highest_vcn+0x3c0/0x410
  Read of size 1 at addr ffff88800e2d5400 by task mount/72
  Call Trace:
   run_get_highest_vcn+0x3c0/0x410
   do_action.isra.0+0x3ba8/0x7b50
   log_replay+0x9ddd/0x10200
   ntfs_loadlog_and_replay+0x4ad/0x610
   ntfs_fill_super+0x214a/0x4540

Fixes: b62567bca474 ("ntfs3: add buffer boundary checks to run_unpack()")
Signed-off-by: Jaeyeong Lee <lee@jaeyeong.cc>
Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
fs/ntfs3/fslog.c
fs/ntfs3/ntfs_fs.h
fs/ntfs3/run.c

index acfa18b84401edd41d3f69befbd9895cb83b178e..0d1be4da3e4f98b02773cfbffb59c5e6bf0d9f42 100644 (file)
@@ -3368,7 +3368,10 @@ move_data:
                memmove(Add2Ptr(attr, aoff), data, dlen);
 
                if (run_get_highest_vcn(le64_to_cpu(attr->nres.svcn),
-                                       attr_run(attr), &t64)) {
+                                       attr_run(attr),
+                                       le32_to_cpu(attr->size) - 
+                                               le16_to_cpu(attr->nres.run_off),        
+                                       &t64)) {
                        goto dirty_vol;
                }
 
index bbf3b6a1dcbee462a68317844fc4b48192befb98..d53febc2559c03ecd5462b1d909eff1a6fa3dc91 100644 (file)
@@ -877,7 +877,8 @@ int run_unpack_ex(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
 #else
 #define run_unpack_ex run_unpack
 #endif
-int run_get_highest_vcn(CLST vcn, const u8 *run_buf, u64 *highest_vcn);
+int run_get_highest_vcn(CLST vcn, const u8 *run_buf, size_t run_buf_size, 
+                      u64 *highest_vcn);
 int run_clone(const struct runs_tree *run, struct runs_tree *new_run);
 bool run_remove_range(struct runs_tree *run, CLST vcn, CLST len, CLST *done);
 CLST run_len(const struct runs_tree *run);
index 1ce7d92fb27482eb91f48582b4b38a70d18842ef..19aa044fd1fcc9de43135c17db05d25d8a6c010b 100644 (file)
@@ -1205,18 +1205,23 @@ int run_unpack_ex(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
  * Return the highest vcn from a mapping pairs array
  * it used while replaying log file.
  */
-int run_get_highest_vcn(CLST vcn, const u8 *run_buf, u64 *highest_vcn)
+int run_get_highest_vcn(CLST vcn, const u8 *run_buf, size_t run_buf_size, 
+                      u64 *highest_vcn)
 {
+       const u8 *run_last = run_buf + run_buf_size;
        u64 vcn64 = vcn;
        u8 size_size;
 
-       while ((size_size = *run_buf & 0xF)) {
+       while (run_buf < run_last && (size_size = *run_buf & 0xF)) {
                u8 offset_size = *run_buf++ >> 4;
                u64 len;
 
                if (size_size > 8 || offset_size > 8)
                        return -EINVAL;
 
+               if (run_buf + size_size + offset_size > run_last) 
+                       return -EINVAL;
+
                len = run_unpack_s64(run_buf, size_size, 0);
                if (!len)
                        return -EINVAL;