Пользовательское приложение уничтожается при передаче строки в драйвер устройства ядра - PullRequest
0 голосов
/ 04 октября 2018

Мы разрабатываем драйвер USB для нашей платы X86 и используем Yocto в качестве операционной системы.Мы столкнулись с одной загадочной проблемой и нуждаемся в вашей помощи, так как мы опробовали все наши варианты. Нам удалось выяснить, к какой строке относится проблема ..

Это код драйвера ядра:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h>    /* for put_user */
#include "test_strlen.h"

/*  
 *  Prototypes - this would normally go in a .h file
 */
int init_module(void);
void cleanup_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
static long test_ioctl(struct file *, unsigned int,
              unsigned long);

#define SUCCESS 0
#define DEVICE_NAME "test_strlen"   /* Dev name as it appears in /proc/devices   */
#define BUF_LEN 80      /* Max length of the message from the device */

/* 
 * Global variables are declared as static, so are global within the file. 
 */

static int Major;       /* Major number assigned to our device driver */
static int Device_Open = 0; /* Is device open?  
                 * Used to prevent multiple access to device */
static char msg[BUF_LEN];   /* The msg the device will give when asked */
static char *msg_Ptr;

static struct file_operations fops = {
    .read = device_read,
    .write = device_write,
    .open = device_open,
    .release = device_release,
    .unlocked_ioctl = test_ioctl 
};

static long test_ioctl(struct file *file, unsigned int cmd,
        unsigned long arg)
{
    switch(cmd) {
        case IOCTL_CP210x_SET_SER_STR:
            {
                int retval = 0;
                unsigned char serial_str[127];
                memset(serial_str, 0x00, sizeof(serial_str));
                retval = copy_from_user(serial_str, (unsigned char *)arg,
                        strlen((unsigned char *)arg));
                printk(KERN_INFO"string from user: %s\n", serial_str);
                if (retval) {
                    printk(KERN_ERR"%s: failed to retrieve user argument %d\n",
                            __func__, retval);
                    return -EFAULT;
                }
                // retval = cp210x_set_serial_str(port, serial_str);
            }
            break;
            default:
                break;
    }

    return 0;
}
/*
 * This function is called when the module is loaded
 */
int init_module(void)
{
        Major = register_chrdev(0, DEVICE_NAME, &fops);

    if (Major < 0) {
      printk(KERN_ALERT "Registering char device failed with %d\n", Major);
      return Major;
    }

    printk(KERN_INFO "I was assigned major number %d. To talk to\n", Major);
    printk(KERN_INFO "the driver, create a dev file with\n");
    printk(KERN_INFO "'mknod /dev/%s c %d 0'.\n", DEVICE_NAME, Major);
    printk(KERN_INFO "Try various minor numbers. Try to cat and echo to\n");
    printk(KERN_INFO "the device file.\n");
    printk(KERN_INFO "Remove the device file and module when done.\n");

    return SUCCESS;
}

/*
 * This function is called when the module is unloaded
 */
void cleanup_module(void)
{
    /* 
     * Unregister the device 
     */
    unregister_chrdev(Major, DEVICE_NAME);
    // if (ret < 0)
        // printk(KERN_ALERT "Error in unregister_chrdev: %d\n", ret);
}

/*
 * Methods
 */

/* 
 * Called when a process tries to open the device file, like
 * "cat /dev/mycharfile"
 */
static int device_open(struct inode *inode, struct file *file)
{
    static int counter = 0;

    if (Device_Open)
        return -EBUSY;

    Device_Open++;
    sprintf(msg, "I already told you %d times Hello world!\n", counter++);
    msg_Ptr = msg;
    try_module_get(THIS_MODULE);

    return SUCCESS;
}

/* 
 * Called when a process closes the device file.
 */
static int device_release(struct inode *inode, struct file *file)
{
    Device_Open--;      /* We're now ready for our next caller */

    /* 
     * Decrement the usage count, or else once you opened the file, you'll
     * never get get rid of the module. 
     */
    module_put(THIS_MODULE);

    return 0;
}

/* 
 * Called when a process, which already opened the dev file, attempts to
 * read from it.
 */
static ssize_t device_read(struct file *filp,   /* see include/linux/fs.h   */
               char *buffer,    /* buffer to fill with data */
               size_t length,   /* length of the buffer     */
               loff_t * offset)
{
    /*
     * Number of bytes actually written to the buffer 
     */
    int bytes_read = 0;

    /*
     * If we're at the end of the message, 
     * return 0 signifying end of file 
     */
    if (*msg_Ptr == 0)
        return 0;

    /* 
     * Actually put the data into the buffer 
     */
    while (length && *msg_Ptr) {

        /* 
         * The buffer is in the user data segment, not the kernel 
         * segment so "*" assignment won't work.  We have to use 
         * put_user which copies data from the kernel data segment to
         * the user data segment. 
         */
        put_user(*(msg_Ptr++), buffer++);

        length--;
        bytes_read++;
    }

    /* 
     * Most read functions return the number of bytes put into the buffer
     */
    return bytes_read;
}

/*  
 * Called when a process writes to dev file: echo "hi" > /dev/hello 
 */
static ssize_t
device_write(struct file *filp, const char *buff, size_t len, loff_t * off)
{
    printk(KERN_ALERT "Sorry, this operation isn't supported.\n");
    return -EINVAL;
}  

И код приложения.

#include <stdio.h>
#include <fcntl.h>
#include <stropts.h>
#include <termios.h>
#include <unistd.h> // to get close() define
#include <sys/ioctl.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include "test_strlen.h"

#define SER_STR "test"
#define PRODUCT_STR "CP2108 Quad USB to UART Bridge Controller"

int err_check(int ret, char* TAG);

int main()
{
    int fd, ret;
    unsigned char set_serial_str[256] = {};
    unsigned char get_serial_str[256] = {};
    unsigned char product_str[256] = {};


    fd = open("/dev/test_strlen", O_RDWR); 

    if (fd == -1) {
        printf( "Error opening port");
        return -99;
    }

    // Set serial string
    strcpy(set_serial_str, SER_STR) ;
    ret = ioctl(fd, IOCTL_CP210x_SET_SER_STR, set_serial_str);
    ret = err_check(ret, "SET_SER_STR");

    close(fd);
    return 0;
}

int err_check(int ret, char* TAG)
{
    if (ret) {
        printf("FAILED - %s: ret = %d\t errno = %d\n", TAG, ret, errno);
        return -9999;
    }
    else {
        printf("SUCCESS - %s\n", TAG);
        return 0;
    }
}

Код работает нормально, когда я запускаю его на виртуальной машине. Но когда я запускаю то же самое на образе yocto (core-image-minimal intel-core-i7-x64), приложение убивается.

dmesg показывает, что происходит сбой при обращении к strlen((unsigned char *)arg)

BUG: unable to handle kernel paging request at 00007ffef2503480
IP: test_ioctl.part.0+0x2d/0xdcc [test_strlen]
PGD 1790e1067 P4D 1790e1067 PUD 17708b067 PMD 179d8b067 PTE 8000000176ee5067
Oops: 0001 [#3] PREEMPT SMP NOPTI
Modules linked in: test_strlen(PO) [last unloaded: test_strlen]
CPU: 1 PID: 13742 Comm: app Tainted: P      D    O    4.14.56-intel-pk-standard #1
Hardware name: NCR Corporation 7746-1410-8801/PX10, BIOS 4.0.5.0 01/17/2018
task: ffff9ca6b7beee40 task.stack: ffffaadb40138000
RIP: 0010:test_ioctl.part.0+0x2d/0xdcc [test_strlen]
RSP: 0018:ffffaadb4013bdd0 EFLAGS: 00010286
RAX: 0000000000000000 RBX: ffff9ca6b90cabd8 RCX: ffffffffffffffff
RDX: 00007ffef2503480 RSI: 00007ffef2503480 RDI: 00007ffef2503480
RBP: ffffaadb4013be60 R08: 0000003b9a5aed80 R09: 0000003b9a5aed80
R10: 0000000000000000 R11: 0000000000000000 R12: ffffaadb4013bdd1
R13: ffff9ca6b903b500 R14: 0000000040018801 R15: 00007ffef2503480
FS:  00007fbecd6224c0(0000) GS:ffff9ca6bfc80000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007ffef2503480 CR3: 000000017a344000 CR4: 00000000003406e0
Call Trace:
 test_ioctl+0x1c/0x20 [test_strlen]
 do_vfs_ioctl+0x99/0x5e0
 ? putname+0x4c/0x60
 SyS_ioctl+0x79/0x90
 do_syscall_64+0x65/0x120
 entry_SYSCALL_64_after_hwframe+0x3d/0xa2
RIP: 0033:0x3b9a2e98c7
RSP: 002b:00007ffef2503468 EFLAGS: 00000202 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 0000003b9a2e98c7
RDX: 00007ffef2503480 RSI: 0000000040018801 RDI: 0000000000000003
RBP: 00007ffef25037a0 R08: 0000003b9a5aed80 R09: 0000003b9a5aed80
R10: 0000000000000573 R11: 0000000000000202 R12: 00000000004005e0
R13: 00007ffef2503880 R14: 0000000000000000 R15: 0000000000000000
Code: 44 00 00 55 31 c0 48 89 fe b9 7f 00 00 00 48 89 e5 41 54 4c 8d a5 71 ff ff ff 53 4c 89 e7 48 83 c4 80 f3 aa 48 83 c9 ff 48 89 f7 <f2> ae 48 89 ca 48 f7 d2 48 8d 5a ff 48 83 fb 7f 48 89 da 77 0d
RIP: test_ioctl.part.0+0x2d/0xdcc [test_strlen] RSP: ffffaadb4013bdd0
CR2: 00007ffef2503480
---[ end trace ea1ff013117d3c15 ]---

Можете ли вы, ребята, помогите мне, почему это происходит наyocto а не на ВМ ..

Спасибо за вашу помощь .. Очень признателен.

1 Ответ

0 голосов
/ 04 октября 2018
copy_from_user(serial_str, (unsigned char *)arg,
                    strlen((unsigned char *)arg));

arg - это указатель пользователя, поэтому использовать на нем strlen() небезопасно.Вместо этого используйте strnlen_user().(И убедитесь, что длина ограничена sizeof(serial_str)!)

Это, вероятно, успешно на вашей виртуальной машине, потому что ваш процессор и / или программное обеспечение для виртуализации не поддерживают SMAP , который обнаруживаети предотвращает этот несоответствующий доступ.

...