diff options
| author | Konstantin Komarov <almaz.alexandrovich@paragon-software.com> | 2026-04-30 14:30:13 +0200 |
|---|---|---|
| committer | Konstantin Komarov <almaz.alexandrovich@paragon-software.com> | 2026-06-02 17:02:23 +0200 |
| commit | bb11485a87fbb2254b62cfed630b699d50e57da8 (patch) | |
| tree | 1c7a703d7e9910939514dafa73a4a226818e599d | |
| parent | f1df9d771df47aa40de6d70949c28720ae1e430d (diff) | |
fs/ntfs3: add bounds check to run_get_highest_vcn()
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>
| -rw-r--r-- | fs/ntfs3/fslog.c | 5 | ||||
| -rw-r--r-- | fs/ntfs3/ntfs_fs.h | 3 | ||||
| -rw-r--r-- | fs/ntfs3/run.c | 9 |
3 files changed, 13 insertions, 4 deletions
diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c index acfa18b84401..0d1be4da3e4f 100644 --- a/fs/ntfs3/fslog.c +++ b/fs/ntfs3/fslog.c @@ -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; } diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index bbf3b6a1dcbe..d53febc2559c 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -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); diff --git a/fs/ntfs3/run.c b/fs/ntfs3/run.c index 1ce7d92fb274..19aa044fd1fc 100644 --- a/fs/ntfs3/run.c +++ b/fs/ntfs3/run.c @@ -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; |
