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>
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;
}
#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);
* 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;