Revision b15fa9224e6e1239414525d8d556d824701849fc authored by Valentin Vidic on 18 October 2021, 22:15:42 UTC, committed by Linus Torvalds on 19 October 2021, 06:22:03 UTC
Starting with kernel 5.11 built with CONFIG_FORTIFY_SOURCE mouting an ocfs2 filesystem with either o2cb or pcmk cluster stack fails with the trace below. Problem seems to be that strings for cluster stack and cluster name are not guaranteed to be null terminated in the disk representation, while strlcpy assumes that the source string is always null terminated. This causes a read outside of the source string triggering the buffer overflow detection. detected buffer overflow in strlen ------------[ cut here ]------------ kernel BUG at lib/string.c:1149! invalid opcode: 0000 [#1] SMP PTI CPU: 1 PID: 910 Comm: mount.ocfs2 Not tainted 5.14.0-1-amd64 #1 Debian 5.14.6-2 RIP: 0010:fortify_panic+0xf/0x11 ... Call Trace: ocfs2_initialize_super.isra.0.cold+0xc/0x18 [ocfs2] ocfs2_fill_super+0x359/0x19b0 [ocfs2] mount_bdev+0x185/0x1b0 legacy_get_tree+0x27/0x40 vfs_get_tree+0x25/0xb0 path_mount+0x454/0xa20 __x64_sys_mount+0x103/0x140 do_syscall_64+0x3b/0xc0 entry_SYSCALL_64_after_hwframe+0x44/0xae Link: https://lkml.kernel.org/r/20210929180654.32460-1-vvidic@valentin-vidic.from.hr Signed-off-by: Valentin Vidic <vvidic@valentin-vidic.from.hr> Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com> Cc: Mark Fasheh <mark@fasheh.com> Cc: Joel Becker <jlbec@evilplan.org> Cc: Junxiao Bi <junxiao.bi@oracle.com> Cc: Changwei Ge <gechangwei@live.cn> Cc: Gang He <ghe@suse.com> Cc: Jun Piao <piaojun@huawei.com> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1 parent 5314454
gup_test.c
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/ktime.h>
#include <linux/debugfs.h>
#include "gup_test.h"
static void put_back_pages(unsigned int cmd, struct page **pages,
unsigned long nr_pages, unsigned int gup_test_flags)
{
unsigned long i;
switch (cmd) {
case GUP_FAST_BENCHMARK:
case GUP_BASIC_TEST:
for (i = 0; i < nr_pages; i++)
put_page(pages[i]);
break;
case PIN_FAST_BENCHMARK:
case PIN_BASIC_TEST:
case PIN_LONGTERM_BENCHMARK:
unpin_user_pages(pages, nr_pages);
break;
case DUMP_USER_PAGES_TEST:
if (gup_test_flags & GUP_TEST_FLAG_DUMP_PAGES_USE_PIN) {
unpin_user_pages(pages, nr_pages);
} else {
for (i = 0; i < nr_pages; i++)
put_page(pages[i]);
}
break;
}
}
static void verify_dma_pinned(unsigned int cmd, struct page **pages,
unsigned long nr_pages)
{
unsigned long i;
struct page *page;
switch (cmd) {
case PIN_FAST_BENCHMARK:
case PIN_BASIC_TEST:
case PIN_LONGTERM_BENCHMARK:
for (i = 0; i < nr_pages; i++) {
page = pages[i];
if (WARN(!page_maybe_dma_pinned(page),
"pages[%lu] is NOT dma-pinned\n", i)) {
dump_page(page, "gup_test failure");
break;
} else if (cmd == PIN_LONGTERM_BENCHMARK &&
WARN(!is_pinnable_page(page),
"pages[%lu] is NOT pinnable but pinned\n",
i)) {
dump_page(page, "gup_test failure");
break;
}
}
break;
}
}
static void dump_pages_test(struct gup_test *gup, struct page **pages,
unsigned long nr_pages)
{
unsigned int index_to_dump;
unsigned int i;
/*
* Zero out any user-supplied page index that is out of range. Remember:
* .which_pages[] contains a 1-based set of page indices.
*/
for (i = 0; i < GUP_TEST_MAX_PAGES_TO_DUMP; i++) {
if (gup->which_pages[i] > nr_pages) {
pr_warn("ZEROING due to out of range: .which_pages[%u]: %u\n",
i, gup->which_pages[i]);
gup->which_pages[i] = 0;
}
}
for (i = 0; i < GUP_TEST_MAX_PAGES_TO_DUMP; i++) {
index_to_dump = gup->which_pages[i];
if (index_to_dump) {
index_to_dump--; // Decode from 1-based, to 0-based
pr_info("---- page #%u, starting from user virt addr: 0x%llx\n",
index_to_dump, gup->addr);
dump_page(pages[index_to_dump],
"gup_test: dump_pages() test");
}
}
}
static int __gup_test_ioctl(unsigned int cmd,
struct gup_test *gup)
{
ktime_t start_time, end_time;
unsigned long i, nr_pages, addr, next;
long nr;
struct page **pages;
int ret = 0;
bool needs_mmap_lock =
cmd != GUP_FAST_BENCHMARK && cmd != PIN_FAST_BENCHMARK;
if (gup->size > ULONG_MAX)
return -EINVAL;
nr_pages = gup->size / PAGE_SIZE;
pages = kvcalloc(nr_pages, sizeof(void *), GFP_KERNEL);
if (!pages)
return -ENOMEM;
if (needs_mmap_lock && mmap_read_lock_killable(current->mm)) {
ret = -EINTR;
goto free_pages;
}
i = 0;
nr = gup->nr_pages_per_call;
start_time = ktime_get();
for (addr = gup->addr; addr < gup->addr + gup->size; addr = next) {
if (nr != gup->nr_pages_per_call)
break;
next = addr + nr * PAGE_SIZE;
if (next > gup->addr + gup->size) {
next = gup->addr + gup->size;
nr = (next - addr) / PAGE_SIZE;
}
switch (cmd) {
case GUP_FAST_BENCHMARK:
nr = get_user_pages_fast(addr, nr, gup->gup_flags,
pages + i);
break;
case GUP_BASIC_TEST:
nr = get_user_pages(addr, nr, gup->gup_flags, pages + i,
NULL);
break;
case PIN_FAST_BENCHMARK:
nr = pin_user_pages_fast(addr, nr, gup->gup_flags,
pages + i);
break;
case PIN_BASIC_TEST:
nr = pin_user_pages(addr, nr, gup->gup_flags, pages + i,
NULL);
break;
case PIN_LONGTERM_BENCHMARK:
nr = pin_user_pages(addr, nr,
gup->gup_flags | FOLL_LONGTERM,
pages + i, NULL);
break;
case DUMP_USER_PAGES_TEST:
if (gup->test_flags & GUP_TEST_FLAG_DUMP_PAGES_USE_PIN)
nr = pin_user_pages(addr, nr, gup->gup_flags,
pages + i, NULL);
else
nr = get_user_pages(addr, nr, gup->gup_flags,
pages + i, NULL);
break;
default:
ret = -EINVAL;
goto unlock;
}
if (nr <= 0)
break;
i += nr;
}
end_time = ktime_get();
/* Shifting the meaning of nr_pages: now it is actual number pinned: */
nr_pages = i;
gup->get_delta_usec = ktime_us_delta(end_time, start_time);
gup->size = addr - gup->addr;
/*
* Take an un-benchmark-timed moment to verify DMA pinned
* state: print a warning if any non-dma-pinned pages are found:
*/
verify_dma_pinned(cmd, pages, nr_pages);
if (cmd == DUMP_USER_PAGES_TEST)
dump_pages_test(gup, pages, nr_pages);
start_time = ktime_get();
put_back_pages(cmd, pages, nr_pages, gup->test_flags);
end_time = ktime_get();
gup->put_delta_usec = ktime_us_delta(end_time, start_time);
unlock:
if (needs_mmap_lock)
mmap_read_unlock(current->mm);
free_pages:
kvfree(pages);
return ret;
}
static long gup_test_ioctl(struct file *filep, unsigned int cmd,
unsigned long arg)
{
struct gup_test gup;
int ret;
switch (cmd) {
case GUP_FAST_BENCHMARK:
case PIN_FAST_BENCHMARK:
case PIN_LONGTERM_BENCHMARK:
case GUP_BASIC_TEST:
case PIN_BASIC_TEST:
case DUMP_USER_PAGES_TEST:
break;
default:
return -EINVAL;
}
if (copy_from_user(&gup, (void __user *)arg, sizeof(gup)))
return -EFAULT;
ret = __gup_test_ioctl(cmd, &gup);
if (ret)
return ret;
if (copy_to_user((void __user *)arg, &gup, sizeof(gup)))
return -EFAULT;
return 0;
}
static const struct file_operations gup_test_fops = {
.open = nonseekable_open,
.unlocked_ioctl = gup_test_ioctl,
};
static int __init gup_test_init(void)
{
debugfs_create_file_unsafe("gup_test", 0600, NULL, NULL,
&gup_test_fops);
return 0;
}
late_initcall(gup_test_init);
Computing file changes ...