From: Guo Xuenan guoxuenan@huawei.com
hulk inclusion category: bugfix bugzilla: 182980 https://gitee.com/openeuler/kernel/issues/I4DDEL
-------------------------------------------------
When using ioctl interface to resize ubi volume, ubi_resize_volume will resize eba table first, but not change vol->reserved_pebs in the same atomic context which may cause concurrency access eba table.
For example, When user do shrink ubi volume A calling ubi_resize_volume, while the other thread is writing (volume B) and triggering wear-leveling, which may calling ubi_write_fastmap, under these circumstances, KASAN may report: slab-out-of-bounds in ubi_eba_get_ldesc+0xfb/0x130.
The main work of this patch include: 1. fix races in ubi_resize_volume and ubi_update_fastmap, to avoid eba_tbl read out of bounds. first, we make eba_tbl and reserved_pebs updating under the protect of vol->volumes_lock. second, rollback volume in case of resize failure. Also mention that for volume shrinking failure, since part of volume has been shrunk and unmapped, there is no need to recover {rsvd/avail}_pebs. 2. fix some memleak in error path of ubi_resize_volume when destroy new_eba_tbl.
BUG: KASAN: slab-out-of-bounds in ubi_eba_get_ldesc+0xfb/0x130 [ubi] Read of size 4 at addr ffff888013ff7170 by task kworker/u16:3/160 CPU: 6 PID: 160 Comm: kworker/u16:3 Not tainted Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.14.0-1.fc33 04/01/2014 Workqueue: writeback wb_workfn (flush-ubifs_0_0) Call Trace: dump_stack+0x9c/0xcf print_address_description.constprop.0+0x1c/0x220 kasan_report.cold+0x1f/0x37 ubi_eba_get_ldesc+0xfb/0x130 [ubi] ubi_update_fastmap.cold+0x6be/0xc6b [ubi] ubi_wl_get_peb+0x2a2/0x580 [ubi] try_write_vid_and_data+0x9a/0x4d0 [ubi] ubi_eba_write_leb+0x780/0x1890 [ubi] ubi_leb_map+0x197/0x2c0 [ubi] ubifs_leb_map+0x139/0x240 [ubifs] ubifs_add_bud_to_log+0xb02/0xea0 [ubifs] make_reservation+0x860/0xb40 [ubifs] ubifs_jnl_write_data+0x461/0x9b0 [ubifs] do_writepage+0x375/0x550 [ubifs] ubifs_writepage+0x3a4/0x670 [ubifs] __writepage+0x61/0x160 write_cache_pages+0x433/0xb70 do_writepages+0x1ad/0x260 __writeback_single_inode+0xb3/0x810 writeback_sb_inodes+0x4d9/0xcc0 __writeback_inodes_wb+0xc1/0x260 wb_writeback+0x585/0x760 wb_workfn+0x751/0xdb0 process_one_work+0x6e5/0xf10 worker_thread+0x5e1/0x10c0 kthread+0x335/0x400 ret_from_fork+0x1f/0x30
Allocated by task 690: kasan_save_stack+0x1b/0x40 __kasan_kmalloc.constprop.0+0xc2/0xd0 ubi_eba_create_table+0x88/0x1a0 [ubi] ubi_resize_volume.cold+0x166/0xc75 [ubi] ubi_cdev_ioctl+0x57f/0x1aa0 [ubi] __x64_sys_ioctl+0x141/0x1a0 do_syscall_64+0x2d/0x40 entry_SYSCALL_64_after_hwframe+0x44/0xa9
The following steps can used to reproduce: Process 1: write and trigger ubi wear-leveling ubimkvol /dev/ubi0 -s 5000MiB -N v1 ubimkvol /dev/ubi0 -s 2000MiB -N v2 ubimkvol /dev/ubi0 -s 10MiB -N v3 mount -t ubifs /dev/ubi0_0 /mnt/ubifs while true; do filename=/mnt/ubifs/$((RANDOM)) dd if=/dev/random of=${filename} bs=1M count=$((RANDOM % 1000)) rm -rf ${filename} sync /mnt/ubifs/ done
Process 2: do random resize struct ubi_rsvol_req req; req.vol_id = 1; req.bytes = (rand() % 50) * 512KB; ioctl(fd, UBI_IOCRSVOL, &req);
Reported-by: Hulk Robot hulkci@huawei.com Signed-off-by: Guo Xuenan guoxuenan@huawei.com Reviewed-by: Zhang Xiaoxu zhangxiaoxu5@huawei.com Reviewed-by: Zhang Yi yi.zhang@huawei.com
Signed-off-by: Chen Jun chenjun102@huawei.com --- drivers/mtd/ubi/vmt.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-)
diff --git a/drivers/mtd/ubi/vmt.c b/drivers/mtd/ubi/vmt.c index 1bc7b3a05604..04d0e217ea1c 100644 --- a/drivers/mtd/ubi/vmt.c +++ b/drivers/mtd/ubi/vmt.c @@ -409,6 +409,7 @@ int ubi_resize_volume(struct ubi_volume_desc *desc, int reserved_pebs) struct ubi_device *ubi = vol->ubi; struct ubi_vtbl_record vtbl_rec; struct ubi_eba_table *new_eba_tbl = NULL; + struct ubi_eba_table *old_eba_tbl = NULL; int vol_id = vol->vol_id;
if (ubi->ro_mode) @@ -454,10 +455,13 @@ int ubi_resize_volume(struct ubi_volume_desc *desc, int reserved_pebs) err = -ENOSPC; goto out_free; } + ubi->avail_pebs -= pebs; ubi->rsvd_pebs += pebs; ubi_eba_copy_table(vol, new_eba_tbl, vol->reserved_pebs); - ubi_eba_replace_table(vol, new_eba_tbl); + old_eba_tbl = vol->eba_tbl; + vol->eba_tbl = new_eba_tbl; + vol->reserved_pebs = reserved_pebs; spin_unlock(&ubi->volumes_lock); }
@@ -465,14 +469,16 @@ int ubi_resize_volume(struct ubi_volume_desc *desc, int reserved_pebs) for (i = 0; i < -pebs; i++) { err = ubi_eba_unmap_leb(ubi, vol, reserved_pebs + i); if (err) - goto out_acc; + goto out_free; } spin_lock(&ubi->volumes_lock); ubi->rsvd_pebs += pebs; ubi->avail_pebs -= pebs; ubi_update_reserved(ubi); ubi_eba_copy_table(vol, new_eba_tbl, reserved_pebs); - ubi_eba_replace_table(vol, new_eba_tbl); + old_eba_tbl = vol->eba_tbl; + vol->eba_tbl = new_eba_tbl; + vol->reserved_pebs = reserved_pebs; spin_unlock(&ubi->volumes_lock); }
@@ -494,27 +500,32 @@ int ubi_resize_volume(struct ubi_volume_desc *desc, int reserved_pebs) if (err) goto out_acc;
- vol->reserved_pebs = reserved_pebs; if (vol->vol_type == UBI_DYNAMIC_VOLUME) { vol->used_ebs = reserved_pebs; vol->last_eb_bytes = vol->usable_leb_size; vol->used_bytes = (long long)vol->used_ebs * vol->usable_leb_size; } - + /* destroy old table */ + ubi_eba_destroy_table(old_eba_tbl); ubi_volume_notify(ubi, vol, UBI_VOLUME_RESIZED); self_check_volumes(ubi); return err;
out_acc: + spin_lock(&ubi->volumes_lock); + vol->reserved_pebs = reserved_pebs - pebs; if (pebs > 0) { - spin_lock(&ubi->volumes_lock); ubi->rsvd_pebs -= pebs; ubi->avail_pebs += pebs; - spin_unlock(&ubi->volumes_lock); + ubi_eba_copy_table(vol, old_eba_tbl, vol->reserved_pebs); + } else { + ubi_eba_copy_table(vol, old_eba_tbl, reserved_pebs); } + vol->eba_tbl = old_eba_tbl; + spin_unlock(&ubi->volumes_lock); out_free: - kfree(new_eba_tbl); + ubi_eba_destroy_table(new_eba_tbl); return err; }