Как правильно отключить SMAP из модуля linux? - PullRequest
0 голосов
/ 13 апреля 2020

Я следую учебному пособию здесь .

У меня есть следующий код:

#include <linux/init.h>           // Macros used to mark up functions e.g. __init __exit
#include <linux/module.h>         // Core header for loading LKMs into the kernel
#include <linux/device.h>         // Header to support the kernel Driver Model
#include <linux/kernel.h>         // Contains types, macros, functions for the kernel
#include <linux/fs.h>             // Header for the Linux file system support
#include <linux/uaccess.h>          // Required for the copy to user function
#define  DEVICE_NAME "ebbchar"    ///< The device will appear at /dev/ebbchar using this value
#define  CLASS_NAME  "ebb"        ///< The device class -- this is a character device driver

MODULE_LICENSE("GPL");            ///< The license type -- this affects available functionality
MODULE_AUTHOR("Derek Molloy");    ///< The author -- visible when you use modinfo
MODULE_DESCRIPTION("A simple Linux char driver for the BBB");  ///< The description -- see modinfo
MODULE_VERSION("0.1");            ///< A version number to inform users

static int    majorNumber;                  ///< Stores the device number -- determined automatically
static char   message[256] = {0};           ///< Memory for the string that is passed from userspace
static short  size_of_message;              ///< Used to remember the size of the string stored
static int    numberOpens = 0;              ///< Counts the number of times the device is opened
static struct class*  ebbcharClass  = NULL; ///< The device-driver class struct pointer
static struct device* ebbcharDevice = NULL; ///< The device-driver device struct pointer

static int     dev_open(struct inode *, struct file *);
static int     dev_release(struct inode *, struct file *);
static ssize_t dev_read(struct file *, char *, size_t, loff_t *);
static ssize_t dev_write(struct file *, const char *, size_t, loff_t *);

static struct file_operations fops =
{
   .open = dev_open,
   .read = dev_read,
   .write = dev_write,
   .release = dev_release,
};

static int __init ebbchar_init(void){
   printk(KERN_INFO "EBBChar: Initializing the EBBChar LKM\n");

   // Try to dynamically allocate a major number for the device -- more difficult but worth it
   majorNumber = register_chrdev(0, DEVICE_NAME, &fops);
   if (majorNumber<0){
      printk(KERN_ALERT "EBBChar failed to register a major number\n");
      return majorNumber;
   }
   printk(KERN_INFO "EBBChar: registered correctly with major number %d\n", majorNumber);

   // Register the device class
   ebbcharClass = class_create(THIS_MODULE, CLASS_NAME);
   if (IS_ERR(ebbcharClass)){                // Check for error and clean up if there is
      unregister_chrdev(majorNumber, DEVICE_NAME);
      printk(KERN_ALERT "Failed to register device class\n");
      return PTR_ERR(ebbcharClass);          // Correct way to return an error on a pointer
   }
   printk(KERN_INFO "EBBChar: device class registered correctly\n");

   // Register the device driver
   ebbcharDevice = device_create(ebbcharClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
   if (IS_ERR(ebbcharDevice)){               // Clean up if there is an error
      class_destroy(ebbcharClass);           // Repeated code but the alternative is goto statements
      unregister_chrdev(majorNumber, DEVICE_NAME);
      printk(KERN_ALERT "Failed to create the device\n");
      return PTR_ERR(ebbcharDevice);
   }
   printk(KERN_INFO "EBBChar: device class created correctly\n"); // Made it! device was initialized
   return 0;
}

static void __exit ebbchar_exit(void){
   device_destroy(ebbcharClass, MKDEV(majorNumber, 0));     // remove the device
   class_unregister(ebbcharClass);                          // unregister the device class
   class_destroy(ebbcharClass);                             // remove the device class
   unregister_chrdev(majorNumber, DEVICE_NAME);             // unregister the major number
   printk(KERN_INFO "EBBChar: Goodbye from the LKM!\n");
}

static int dev_open(struct inode *inodep, struct file *filep){
   numberOpens++;
   printk(KERN_INFO "EBBChar: Device has been opened %d time(s)\n", numberOpens);
   return 0;
}

static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset){
   int error_count = 0;
   // copy_to_user has the format ( * to, *from, size) and returns 0 on success
   error_count = copy_to_user(buffer, message, size_of_message);

   if (error_count==0){            // if true then have success
      printk(KERN_INFO "EBBChar: Sent %d characters to the user\n", size_of_message);
      return (size_of_message=0);  // clear the position to the start and return 0
   }
   else {
      printk(KERN_INFO "EBBChar: Failed to send %d characters to the user\n", error_count);
      return -EFAULT;              // Failed -- return a bad address message (i.e. -14)
   }
}

static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset){
   sprintf(message, "%s(%zu letters)", buffer, len);   // appending received string with its length
   size_of_message = strlen(message);                 // store the length of the stored message
   printk(KERN_INFO "EBBChar: Received %zu characters from the user\n", len);
   return len;
}

static int dev_release(struct inode *inodep, struct file *filep){
   printk(KERN_INFO "EBBChar: Device successfully closed\n");
   return 0;
}

module_init(ebbchar_init);
module_exit(ebbchar_exit);

У меня также есть небольшой файл тестирования из учебника. Проблема заключается в том, что при выполнении тестового кода процесс завершается уничтожением. В файлах журналов говорится, что это происходит из-за доступа в режиме супервизора и что было сгенерировано исключение сбоя страницы.

После некоторых исследований и поиска в файлах журналов возникла проблема совместимости с Supervisor Mode Access Prevention, где код ядра может доступ к пользовательскому коду из-за новой функции SMAP некоторых процессоров.

После отключения SMAP во время загрузки с опцией nosmap тестовый код работает нормально.

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

Я думаю, что функция copy_to_user() - хороший пример. Проблема возникает, когда запись называется. Может ли кто-нибудь указать мне, как правильно кодировать функцию write() для этого модуля?

1 Ответ

2 голосов
/ 14 апреля 2020

Если у вас возникла проблема, отключение SMAP не решит ее , только скроет ее . Тот факт, что SMAP убивает ваш процесс, хорош, и он должен оставаться таким, это мера безопасности ядра Linux, и его не следует отключать только для того, чтобы модуль с ошибками работал.

Ваша ошибка здесь:

sprintf(message, "%s(%zu letters)", buffer, len);

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

Вы должны использовать copy_from_user(), поскольку вы имеете дело с буфером пространства пользователя:

static ssize_t dev_write(struct file *filep, const char __user *buffer, size_t len, loff_t *offset){
    unsigned long remaining;

    // NEVER copy more than your message buffer size.
    if (len > 256)
        len = 256;

    // Ensure that the additional string fits (XXX because len is at most 3 chars).
    if (len + strlen(" (XXX letters)") >= 256) {
        pr_info("User buffer is too big (%zu).\n", len);
        return -EINVAL;
    }

    remaining = copy_from_user(message, buffer, len); 
    if (remaining > 0) {
        pr_info("Failed to copy %lu characters from the user.\n", remaining);
        return -EFAULT;
    }

    sprintf(message + len, " (%zu letters)", len);
    size_of_message = len + strlen(message + len);

    pr_info("Received %zu characters from the user.\n", len);
    return len;
}

Несколько других советов:

  • error_count должно быть unsigned long вместо int, поскольку copy_to_user() возвращает этот тип.

  • Ваши dev_read() и dev_write() функции берут указатели из пространства пользователя. Каждый раз, когда функция ядра получает указатель из пользовательского пространства, этот указатель должен быть объявлен с использованием аннотации __user, как я делал в функции выше.

  • Вы можете использовать макрос pr_info() вместо printk(KERN_INFO ...), также как я делал выше.

  • Вы можете избежать записи имени модуля (EBBChar:) каждый раз в начало каждой строки, просто переопределив макрос pr_fmt следующим образом:

    // This will make any pr_* function (except pr_cont) prepend the module name to each message.
    // KBUILD_MODNAME is automatically generated when building and is your module name.
    // Put this at the top of your module.
    
    #ifdef pr_fmt
    #undef pr_fmt
    #endif
    #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
    
...