KASAN 打电话时抱怨 copy_from/to_user

KASAN complains when calling copy_from/to_user

我们正在开发一个 linux 驱动程序,当我 read/write 创建设备文件时注意到 KASAN 抱怨。

下面列出了最小的例子(所以设计得不好)。 它创建文件 /dev/test_ctl 并启用 read/write/ioctl.

我们编译了一个启用了 KASAN 的 4.6.2 内核,并且这个代码在更新的 Fedora 中。 在modprobe模块之后,我尝试读取(cat)/写入(echo foo >)并调用ioctl,都得到了BUG: KASAN: user-memory-access on address ... ...。我预计这些操作会在没有任何警告的情况下运行。

根据 pr_info,我注意到该行为是由 copy_to/from_user 函数引起的。 由于我们使用 KASAN 进行内存相关的运行时检查,我们如何才能消除这种噪音。

#define pr_fmt(fmt) "test dev : " fmt

#include <linux/module.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm-generic/uaccess.h>
#include <asm-generic/ioctl.h>
#include <linux/spinlock.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("FOO");
MODULE_DESCRIPTION("BAR");


#define LEN 1024
#define IOCTL_READ _IOR('t', 0xD0, u8[LEN])
#define IOCTL_WRITE _IOW('t', 0xD1, u8[LEN])

static u8 buf[LEN];
static dev_t major;
static struct class *class;
static struct cdev cdev;
static struct device *cdevice;

static long test_ioctl(struct file *file, unsigned int cmd,
            unsigned long arg)
{
    long err;
    void __user *ptr = (void __user *)arg;

    pr_info("ioctl: cmd: %08x, arg: %p, local: %p\n", cmd, ptr, buf);

    switch (cmd) {
    case IOCTL_READ:
            pr_info("ioctl/r: calling copy_to_user\n");
            if (copy_to_user(ptr, buf, LEN)) {
                    pr_info("ioctl/r: failed to copy to user\n");
                    err = -EFAULT;
            } else {
                    pr_info("ioctl/r: buffer copied, val: %8ph\n", buf);
                    err = 0;
            }
            break;
    case IOCTL_WRITE:
            pr_info("ioctl/r: calling copy_from_user\n");
            if (copy_from_user(buf, ptr, LEN)) {
                    pr_info("ioctl/w: failed to copy from user\n");
                    err = -EFAULT;
            } else {
                    pr_info("ioctl/w: buffer copied, val: %8ph\n", buf);
                    err = 0;
            }
            break;
    default:
            pr_info("ioctl: invalid command\n");
            err = -EINVAL;
            break;
    }
    return err;
}

static int test_open(struct inode *inode, struct file *file)
{
    return 0;
}

static int test_release(struct inode *inode, struct file *file)
{
    return 0;
}

static ssize_t test_read(struct file *filep,
            char __user *ptr, size_t len, loff_t *offset)
{
    ssize_t r;
    if (*offset) {
            return 0;
    }
    if (len > LEN) {
            len = LEN;
    }
    *offset += len;
    pr_info("calling copy_to_user\n");
    r = copy_to_user(ptr, buf, len) ? -EFAULT : len;
    pr_info("called copy_to_user\n");
    return r;
}

static ssize_t test_write(struct file *filep,
            const char __user *ptr, size_t len, loff_t *offset)
{
    ssize_t r;
    if (*offset) {
            return -EINVAL;
    }
    if (len > LEN) {
            len = LEN;
    }
    *offset += len;
    pr_info("calling copy_from_user\n");
    r = copy_from_user(buf, ptr, len) ? -EFAULT : len;
    pr_info("called copy_from_user\n");
    return r;
}

static const struct file_operations cdev_ops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = test_ioctl,
    .open = test_open,
    .release = test_release,
    .read = test_read,
    .write = test_write,
};

static int test_init(void)
{
    int rc;

    pr_info("test device initing\n");

    rc = alloc_chrdev_region(&major, 0, 1, "test");

    if (rc) {
            goto fail_alloc_chrdev_region;
    }
    pr_info("major assigned: %d\n", (int)MAJOR(major));

    class = class_create(THIS_MODULE, "test_ctl");
    if (IS_ERR(class)) {
            pr_err("failed to create class\n");
            rc = PTR_RET(class);
            goto fail_create_class;
    }

    cdev_init(&cdev, &cdev_ops);
    cdev.owner = THIS_MODULE;
    rc = cdev_add(&cdev, MKDEV(MAJOR(major), 0), 1);
    if (rc) {
            pr_info("failed to add char dev\n");
            goto fail_cdev_add;
    }

    cdevice = device_create(class, NULL, MKDEV(MAJOR(major), 0), NULL,
                    "test_ctl");
    if (IS_ERR(cdevice)) {
            pr_err("failed to create /dev/ file\n");
            rc = PTR_RET(cdevice);
            goto fail_device_create;
    }
    pr_info("driver initialized\n");

    return 0;

    device_destroy(class, MKDEV(major, 0));
fail_device_create:
    cdev_del(&cdev);
fail_cdev_add:
    class_destroy(class);
fail_create_class:
    unregister_chrdev_region(major, 1);
fail_alloc_chrdev_region:
    return rc;
}

module_init(test_init);

这是 od /dev/test_ctl

期间的内核消息
[18088.583185] test dev : called copy_to_user
[18117.378665] test dev : ioctl: cmd: 00005401, arg: 00007ffeb2303070, local: ffffffffa018aba0
[18117.380954] test dev : ioctl: invalid command
[18117.386294] test dev : calling copy_to_user
[18117.388772] ==================================================================
[18117.390903] BUG: KASAN: user-memory-access on address 00007f52831a8000
[18117.392150] Write of size 1024 by task od/2057
[18117.393305] CPU: 1 PID: 2057 Comm: od Tainted: G    B      O    4.6.2-kasan-outline #1
[18117.395448] Hardware name: Bochs Bochs, BIOS Bochs 01/01/2011
[18117.396655]  ffff880009cf8000 00000000da76c32b ffff88000a2efbc8 ffffffff816e1398
[18117.399220]  0000000000000400 ffff88000a2efc60 ffff88000a2efc50 ffffffff8134fe46
[18117.401765]  0000000000000246 ffffffffa0189140 0000000000000292 ffff88000a2efc00
[18117.404304] Call Trace:
[18117.405375]  [<ffffffff816e1398>] dump_stack+0x85/0xcd
[18117.406550]  [<ffffffff8134fe46>] kasan_report_error+0x456/0x560
[18117.407777]  [<ffffffff81197896>] ? debug_lockdep_rcu_enabled+0x26/0x40
[18117.409039]  [<ffffffff813504e8>] kasan_report+0x58/0x60
[18117.410222]  [<ffffffff8134f498>] ? memcpy+0x28/0x40
[18117.411394]  [<ffffffff8134f08d>] __asan_storeN+0x12d/0x180
[18117.412592]  [<ffffffff8134f498>] memcpy+0x28/0x40
[18117.413766]  [<ffffffffa0188019>] __copy_to_user+0x9/0x10 [test_drv]
[18117.415010]  [<ffffffffa0188132>] test_read+0x72/0xa0 [test_drv]
[18117.416233]  [<ffffffff8138529d>] __vfs_read+0xdd/0x260
[18117.417419]  [<ffffffff813851c0>] ? vfs_iter_write+0x190/0x190
[18117.418644]  [<ffffffff813f7140>] ? __fsnotify_update_child_dentry_flags.part.1+0x160/0x160
[18117.442467]  [<ffffffff812e0117>] ? vm_mmap_pgoff+0x167/0x1a0
[18117.443686]  [<ffffffff8116ff18>] ? up_write+0x28/0x50
[18117.444874]  [<ffffffff812e0117>] ? vm_mmap_pgoff+0x167/0x1a0
[18117.446093]  [<ffffffff81636dd5>] ? security_file_permission+0xd5/0x100
[18117.447344]  [<ffffffff81386bb7>] vfs_read+0xb7/0x1a0
[18117.448536]  [<ffffffff81388dba>] SyS_read+0xba/0x150
[18117.449713]  [<ffffffff81388d00>] ? vfs_copy_file_range+0x370/0x370
[18117.450945]  [<ffffffff811776c6>] ? trace_hardirqs_on_caller+0x16/0x290
[18117.452193]  [<ffffffff8100401b>] ? trace_hardirqs_on_thunk+0x1b/0x1d
[18117.453441]  [<ffffffff81b386fc>] entry_SYSCALL_64_fastpath+0x1f/0xbd
[18117.454689]  [<ffffffff811717b6>] ? trace_hardirqs_off_caller+0x16/0x120
[18117.455943] ==================================================================
[18117.462008] test dev : called copy_to_user

调用 echo 1 > /dev/test_ctl 时,我收到以下内核消息:

[18212.023598] test dev : calling copy_from_user
[18212.024844] ==================================================================
[18212.027020] BUG: KASAN: user-memory-access on address 00007f31bf34f000
[18212.028272] Read of size 2 by task bash/1982
[18212.029425] CPU: 1 PID: 1982 Comm: bash Tainted: G    B      O    4.6.2-kasan-outline #1
[18212.031585] Hardware name: Bochs Bochs, BIOS Bochs 01/01/2011
[18212.032904]  ffff8800306e0000 000000003803bec2 ffff88000a2dfbc0 ffffffff816e1398
[18212.035569]  0000000000000002 ffff88000a2dfc58 ffff88000a2dfc48 ffffffff8134fe46
[18212.038169]  0000000000000246 ffffffffa0189040 0000000000000286 ffff88000a2dfbf8
[18212.040760] Call Trace:
[18212.041835]  [<ffffffff816e1398>] dump_stack+0x85/0xcd
[18212.043032]  [<ffffffff8134fe46>] kasan_report_error+0x456/0x560
[18212.044276]  [<ffffffff81197896>] ? debug_lockdep_rcu_enabled+0x26/0x40
[18212.045538]  [<ffffffff813504e8>] kasan_report+0x58/0x60
[18212.046742]  [<ffffffff8134f48d>] ? memcpy+0x1d/0x40
[18212.047944]  [<ffffffff8134ef0a>] __asan_loadN+0x12a/0x180
[18212.049155]  [<ffffffff8134f48d>] memcpy+0x1d/0x40
[18212.050340]  [<ffffffffa0188019>] __copy_to_user+0x9/0x10 [test_drv]
[18212.051593]  [<ffffffffa0188097>] test_write+0x77/0xa0 [test_drv]
[18212.052840]  [<ffffffff813854fd>] __vfs_write+0xdd/0x260
[18212.054040]  [<ffffffff81385420>] ? __vfs_read+0x260/0x260
[18212.055252]  [<ffffffff81300b40>] ? __pmd_alloc+0x250/0x250
[18212.056464]  [<ffffffff813b9595>] ? __fd_install+0x5/0x3f0
[18212.057736]  [<ffffffff813b92ac>] ? __alloc_fd+0x3c/0x2b0
[18212.058952]  [<ffffffff81197896>] ? debug_lockdep_rcu_enabled+0x26/0x40
[18212.060220]  [<ffffffff81197896>] ? debug_lockdep_rcu_enabled+0x26/0x40
[18212.061488]  [<ffffffff81636d68>] ? security_file_permission+0x68/0x100
[18212.062757]  [<ffffffff81386d96>] vfs_write+0xf6/0x260
[18212.063959]  [<ffffffff81388f0a>] SyS_write+0xba/0x150
[18212.065165]  [<ffffffff81388e50>] ? SyS_read+0x150/0x150
[18212.066382]  [<ffffffff811776c6>] ? trace_hardirqs_on_caller+0x16/0x290
[18212.067666]  [<ffffffff8100401b>] ? trace_hardirqs_on_thunk+0x1b/0x1d
[18212.068933]  [<ffffffff81b386fc>] entry_SYSCALL_64_fastpath+0x1f/0xbd
[18212.070199]  [<ffffffff811717b6>] ? trace_hardirqs_off_caller+0x16/0x120
[18212.071470] ==================================================================
[18212.073790] test dev : called copy_from_user

所以 KASAN 抱怨 user-space 访问。我不知道为什么。但是 #include <asm-generic/uaccess.h> 看起来 可疑 :外部代码(如模块)应该很少包含 asm-specific header。使用标准 #include <linux/uaccess.h>。顺便说一句,对于 x86 asm-generic 版本的 uaccess.h 从未使用过:它的 asm/uaccess.h header 手动定义了用户访问函数。这可能是您遇到问题的原因。 asm-generic/ioctl.h 包含也是如此。 –齐瓦列夫

感谢您的帮助。我使用 linux/uaccess.h 而不是 asm-generic/uaccess.h 并且此行为不会再次发生。 – OstCollector