如何从 linux 模块正确禁用 SMAP?

How to properly disable SMAP from a linux module?

我正在学习 here 的教程。

我有以下代码:

#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);

我还有一个来自教程的小测试文件。问题是当测试代码 运行s 时,进程最终被杀死。日志文件说这是由于管理员模式访问导致的,并且抛出了页面错误异常。

经过一些研究和查看日志文件归结为与管理模式访问保护的兼容性问题,由于某些 CPU 的新 SMAP 功能,内核代码无法访问用户代码。

在启动时使用 nosmap 选项禁用 SMAP 后,测试代码工作正常。

我正在寻找一种在模块代码中 disable/circumvent 正确 SMAP 的方法。由于此应用程序可以 运行 在多个 CPU 上,我认为更改 CR4 寄存器不是正确的方法。

我认为 copy_to_user() 函数是一个很好的引导。调用 write 时会出现问题。谁能告诉我为这个模块编写 write() 函数的正确方法是什么?

如果您有问题,禁用 SMAP 不会解决问题,它只会隐藏它。 SMAP 终止您的进程这一事实是好的,它应该保持这种状态,这是 Linux 内核的一项安全措施,不应该仅仅为了让有问题的模块工作而禁用它。

你的错误在这里:

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

您正在从内核 space 读取用户 space 内存,这是错误的,并且 SMAP 会阻止此生成错误。

您应该使用 copy_from_user(),因为您正在处理用户 space 缓冲区:

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() returns 那个类型。

  • 您的 dev_read()dev_write() 函数从用户 space 获取指针。任何时候内核函数接受来自用户 space 的指针时,都应使用 __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