2022-05-10 12:11:16

by butt3rflyh4ck

[permalink] [raw]
Subject: A slab-out-of-bounds Write when invoke udf_write_fi via ioctl

Hi, if mounts a malicious udf image, there is a slab out of bounds
write bug when a user invokes udf_write_fi via ioctl.
I have reproduced it in the latest kernel.

##smaple analyse
the function call chains:
do_sys_open
--->do_sys_openat2
--->do_filp_open
--->path_openat
--->open_last_lookups
--->lookup_open
--->udf_add_nondir
--->udf_add_entry

There would traverse to get a `fi` in the function udf_add_entry.
```
if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { [1]
block = dinfo->i_location.logicalBlockNum;
fi = (struct fileIdentDesc *)
(dinfo->i_data + fibh->soffset -
udf_ext0_offset(dir) +
dinfo->i_lenEAttr);
[2]
} else {
block = eloc.logicalBlockNum +
((elen - 1) >>
dir->i_sb->s_blocksize_bits);
fi = (struct fileIdentDesc *)
(fibh->sbh->b_data + fibh->soffset);
}
```
[1] if dinfo->i_alloc_type is ICBTAG_FLAG_AD_IN_ICB, [2] it would
calculate an offset as `fi`,
through the debugger, the `fi` is as below:
```
p/x *(struct fileIdentDesc*)fi
$24 = {
descTag = {
tagIdent = 0x2f70,
descVersion = 0xea55,
tagChecksum = 0xcd,
reserved = 0x66,
tagSerialNum = 0x511f,
descCRC = 0x5a9c,
descCRCLength = 0x5142,
tagLocation = 0x373ce06a
},
fileVersionNum = 0x3139,
fileCharacteristics = 0xf6,
lengthFileIdent = 0x7e,
icb = {
extLength = 0x6059792,
extLocation = {
logicalBlockNum = 0x73886466,
partitionReferenceNum = 0x7cc6
},
impUse = {0x3c, 0xcc, 0x4a, 0xed, 0xdc, 0xfb}
},
lengthOfImpUse = 0x1a6a,
impUse = 0xffff888019ca716a
}
```
These data are wrong and all data are part of udf image mounted.
Then next it would invoke function udf_write_fi to write fileident into `fi`.
```
if (fileident) {
if (adinicb || (offset + lfi < 0)) {
memcpy(udf_get_fi_ident(sfi), fileident, lfi); [3]
} else if (offset >= 0) {
memcpy(fibh->ebh->b_data + offset, fileident, lfi);
} else {
memcpy(udf_get_fi_ident(sfi), fileident, -offset);
memcpy(fibh->ebh->b_data, fileident - offset,
lfi + offset);
}
}
```
The fileident was controlled by user. `sfi` is `fi`, the memcpy
function is called to copy the data, so an out-of-bounds write occurs

##reproduce
CONFIG_KASAN is opened.
[ 27.437559][ T4631] loop0: detected capacity change from 0 to 5376
[ 27.440865][ T4631] UDF-fs: warning (device loop0): udf_load_vrs:
No anchor found
[ 27.441638][ T4631] UDF-fs: Scanning with blocksize 512 failed
[ 27.444203][ T4631] UDF-fs: INFO Mounting volume 'LinuxUDF',
timestamp 2020/09/19 18:44 (1000)
[ 27.462341][ T4631]
==================================================================
[ 27.463081][ T4631] BUG: KASAN: slab-out-of-bounds in
udf_write_fi+0x353/0x1170
[ 27.463778][ T4631] Write of size 165 at addr ffff888014fe5c98 by
task udf_write_fi1/4631
[ 27.464524][ T4631]
[ 27.464736][ T4631] CPU: 0 PID: 4631 Comm: udf_write_fi1 Not
tainted 5.18.0-rc5-00028-ga7391ad35724 #47
[ 27.465598][ T4631] Hardware name: QEMU Standard PC (i440FX + PIIX,
1996), BIOS 1.14.0-2 04/01/2014
[ 27.466427][ T4631] Call Trace:
[ 27.466721][ T4631] <TASK>
[ 27.466990][ T4631] dump_stack_lvl+0x11c/0x1b4
[ 27.467416][ T4631] print_address_description.constprop.0.cold+0xeb/0x48f
[ 27.468051][ T4631] ? udf_write_fi+0x353/0x1170
[ 27.468481][ T4631] kasan_report.cold+0xf4/0x1c6
[ 27.468914][ T4631] ? udf_get_fileident+0x271/0x2d0
[ 27.469371][ T4631] ? udf_write_fi+0x353/0x1170
[ 27.469801][ T4631] kasan_check_range+0x13d/0x180
[ 27.470246][ T4631] memcpy+0x39/0x60
[ 27.470588][ T4631] udf_write_fi+0x353/0x1170
[ 27.470998][ T4631] ? udf_new_tag+0x1fd/0x3c0
[ 27.471412][ T4631] udf_add_entry+0x10c7/0x24d0
[ 27.471822][ T4631] ? udf_write_fi+0x1170/0x1170
[ 27.472218][ T4631] ? __mark_inode_dirty+0x76a/0xb00
[ 27.472643][ T4631] ? lock_downgrade+0x810/0x810
[ 27.473045][ T4631] udf_add_nondir+0xf8/0x410
[ 27.473424][ T4631] ? udf_rename+0x1050/0x1050
[ 27.473806][ T4631] ? userns_owner+0x30/0x30
[ 27.474177][ T4631] ? rcu_read_lock_sched_held+0x3a/0xa0
[ 27.474628][ T4631] ? __mark_inode_dirty+0x5f8/0xb00
[ 27.475051][ T4631] ? udf_get_parent+0x1c0/0x1c0
[ 27.475455][ T4631] lookup_open.isra.0+0xec3/0x1760
[ 27.475876][ T4631] ? lock_release+0x890/0x890
[ 27.476273][ T4631] ? path_lookupat+0x6b0/0x6b0
[ 27.476670][ T4631] ? __mnt_want_write+0x184/0x260
[ 27.477107][ T4631] path_openat+0x7d5/0x2360
[ 27.477480][ T4631] ? lookup_open.isra.0+0x1760/0x1760
[ 27.477915][ T4631] ? lockdep_hardirqs_on_prepare+0x430/0x430
[ 27.478412][ T4631] ? lockdep_hardirqs_on_prepare+0x430/0x430
[ 27.478901][ T4631] do_filp_open+0x199/0x3d0
[ 27.479271][ T4631] ? find_held_lock+0x2d/0x110
[ 27.479663][ T4631] ? may_open_dev+0xd0/0xd0
[ 27.480036][ T4631] ? rwlock_bug.part.0+0x90/0x90
[ 27.480429][ T4631] ? _raw_spin_unlock+0x24/0x40
[ 27.480825][ T4631] ? alloc_fd+0x1ef/0x560
[ 27.481173][ T4631] ? getname_flags.part.0+0x89/0x440
[ 27.481602][ T4631] do_sys_openat2+0x11e/0x3f0
[ 27.481978][ T4631] ? build_open_flags+0x490/0x490
[ 27.482384][ T4631] ? lock_downgrade+0x810/0x810
[ 27.482777][ T4631] __x64_sys_openat+0x11b/0x1d0
[ 27.483188][ T4631] ? __ia32_sys_open+0x190/0x190
[ 27.483594][ T4631] ? lockdep_hardirqs_on_prepare+0x22b/0x430
[ 27.484177][ T4631] ? syscall_enter_from_user_mode+0x21/0x80
[ 27.484890][ T4631] ? lockdep_hardirqs_on+0x8b/0x110
[ 27.485538][ T4631] do_syscall_64+0x35/0xb0
[ 27.486095][ T4631] entry_SYSCALL_64_after_hwframe+0x44/0xae
[ 27.486692][ T4631] RIP: 0033:0x44914d
[ 27.487019][ T4631] Code: 02 b8 ff ff ff ff c3 66 0f 1f 44 00 00 f3
0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b
4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 c0 ff ff 8
[ 27.488797][ T4631] RSP: 002b:00007ffc787d2f68 EFLAGS: 00000246
ORIG_RAX: 0000000000000101
[ 27.489518][ T4631] RAX: ffffffffffffffda RBX: 0000000000400540
RCX: 000000000044914d
[ 27.490179][ T4631] RDX: 0000000000000040 RSI: 0000000020000140
RDI: 0000000000000005
[ 27.490825][ T4631] RBP: 00007ffc787d2f80 R08: 0000000020000170
R09: 0000000000000000
[ 27.491469][ T4631] R10: 0000000000000000 R11: 0000000000000246
R12: 0000000000404dd0
[ 27.492125][ T4631] R13: 0000000000000000 R14: 00000000004c0018
R15: 0000000000400540

If needed a reproduce, I can provided to you.




Regards,
butt3rflyh4ck.




--
Active Defense Lab of Venustech


2022-05-10 16:58:42

by Jan Kara

[permalink] [raw]
Subject: Re: A slab-out-of-bounds Write when invoke udf_write_fi via ioctl

On Tue 10-05-22 15:14:42, butt3rflyh4ck wrote:
> Hi, if mounts a malicious udf image, there is a slab out of bounds
> write bug when a user invokes udf_write_fi via ioctl.
> I have reproduced it in the latest kernel.
>
> ##smaple analyse
> the function call chains:
> do_sys_open
> --->do_sys_openat2
> --->do_filp_open
> --->path_openat
> --->open_last_lookups
> --->lookup_open
> --->udf_add_nondir
> --->udf_add_entry
>
> There would traverse to get a `fi` in the function udf_add_entry.
> ```
> if (dinfo->i_alloc_type == ICBTAG_FLAG_AD_IN_ICB) { [1]
> block = dinfo->i_location.logicalBlockNum;
> fi = (struct fileIdentDesc *)
> (dinfo->i_data + fibh->soffset -
> udf_ext0_offset(dir) +
> dinfo->i_lenEAttr);
> [2]
> } else {
> block = eloc.logicalBlockNum +
> ((elen - 1) >>
> dir->i_sb->s_blocksize_bits);
> fi = (struct fileIdentDesc *)
> (fibh->sbh->b_data + fibh->soffset);
> }
> ```
> [1] if dinfo->i_alloc_type is ICBTAG_FLAG_AD_IN_ICB, [2] it would
> calculate an offset as `fi`,
> through the debugger, the `fi` is as below:
> ```
> p/x *(struct fileIdentDesc*)fi
> $24 = {
> descTag = {
> tagIdent = 0x2f70,
> descVersion = 0xea55,
> tagChecksum = 0xcd,
> reserved = 0x66,
> tagSerialNum = 0x511f,
> descCRC = 0x5a9c,
> descCRCLength = 0x5142,
> tagLocation = 0x373ce06a
> },
> fileVersionNum = 0x3139,
> fileCharacteristics = 0xf6,
> lengthFileIdent = 0x7e,
> icb = {
> extLength = 0x6059792,
> extLocation = {
> logicalBlockNum = 0x73886466,
> partitionReferenceNum = 0x7cc6
> },
> impUse = {0x3c, 0xcc, 0x4a, 0xed, 0xdc, 0xfb}
> },
> lengthOfImpUse = 0x1a6a,
> impUse = 0xffff888019ca716a
> }
> ```
> These data are wrong and all data are part of udf image mounted.
> Then next it would invoke function udf_write_fi to write fileident into `fi`.
> ```
> if (fileident) {
> if (adinicb || (offset + lfi < 0)) {
> memcpy(udf_get_fi_ident(sfi), fileident, lfi); [3]
> } else if (offset >= 0) {
> memcpy(fibh->ebh->b_data + offset, fileident, lfi);
> } else {
> memcpy(udf_get_fi_ident(sfi), fileident, -offset);
> memcpy(fibh->ebh->b_data, fileident - offset,
> lfi + offset);
> }
> }
> ```
> The fileident was controlled by user. `sfi` is `fi`, the memcpy
> function is called to copy the data, so an out-of-bounds write occurs

Thanks for report and the analysis. This is indeed a serious bug. I'll send
a fix shortly.

Honza
--
Jan Kara <[email protected]>
SUSE Labs, CR