[Linux LTS Kernel Bug] [v5.15] KASAN-stack-out-of-bounds-Read-in-gfs2_file_buffered_write

From: Zhang Zhiyu
Date: Sun Mar 03 2024 - 13:22:47 EST


Hi Linux Developers and Maintainers,

We found a bug "KASAN-stack-out-of-bounds-Read-in-gfs2_file_buffered_write"
while fuzzing Linux LTS Kernel 5.15.140 with our modified fuzzer
based on Syzkaller. We have analyzed the root cause and possible
impacts of the bug. And the bug can be steadily reproduced on the
latest LTS kernel 5.15.149. The kernel configs with/without
sanitizers, reports and poc are packed and shared through google
drive:
https://drive.google.com/drive/folders/1OdsYV_-9UMJ_ibwK8zeBSTus7j3lwIVU?usp=sharing

=================== Crash Reproduction ===================
1. Compiled the latest LTS linux-5.15.149 with provided config
(with/without sanitizers)
2. Create an brand new image through syzkaller's create-image.sh and
boot it with the kernel.
3. Copy the repro.c to the vm though scp and compile it with gcc in the vm.
4. Execute the binary of repro and observe the kernel panic.

=================== Cause Analysis ===================
The kernel panic happens in the vulnerable function
fs/gfs2/file.c:784:should_fault_in_pages. The function call stack is:
get_signal->do_coredump->elf_core_dump->dump_emit->__dump_emit->__kernel_write->gfs_file_write_iter->gfs2_file_buffered_write->should_fault_in_pages.

The out-of-bound read occurs at fs/gfs2/file.c:784 "char __user *p =
i->iov[0].iov_base + i->iov_offset;", where i->iov is actually NULL.
We can verify this through debugging with gdb:

pwndbg > p from->iov[0]
$16 = {
iov_base = 0xffffffff8165b44a <exit_to_user_mode_prepare+426>,
iov_len = 18446744071722249801
}

The value of from->iov[0]->iov_len is a meaningless value, indicating
that iov is uninitialized. The from is actually introduced by "if
(should_fault_in_pages(ret, from, &prev_count, &window_size)) {" at
fs/gfs2/file.c:1069:gfs2_file_buffered_write with a blank member iov.

=================== Impact Analysis ===================
In "char __user *p = i->iov[0].iov_base + i->iov_offset;", p oob read
8 bytes through i->iov[0].iov_base with an offset. According to
"*window_size = (size_t)PAGE_SIZE * pages - offset_in_page(p);", the
infomation may be leaked to user space through parameter window_size
if offset_in_page(p) is computable. The implementation of
offset_in_pages is:

#define PAGE_SIZE (__IA64_UL_CONST(1) << PAGE_SHIFT)
#define PAGE_MASK (~(PAGE_SIZE - 1))
#define offset_in_page(p) ((unsigned long)(p) & ~PAGE_MASK)

Macro PAGE_SHIFT is decided by the architecture, take IA64 as an
example, it can be 12, 13, 14, 16. Therefore, offset_in_page may leak
the lower 12-16 bits of p information. Since the repro can steadily
crash the kernel, the recommended scores of CIA impact matrics are
High, None, High.

=================== Patch recommendation ===================
Adding checks before usage is one of the solution. Blocking r/w to
mounted block devices may be another solution, like
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6f861765464f43a71462d52026fbddfc858239a5.
This commit has been merged to linux-6.8-rc6 and can avoid the crash.
However, the linux-5.15.y are still vulnerable to this flaw.


Hope this would be of help to improve the security of Linux kernel.
And if needed, I am happy to provide further assistance.

Best,
Zhiyu Zhang