Неправильный доступ к памяти: почему мое ядро ​​* не * не падает - PullRequest
1 голос
/ 15 января 2020

Я хотел показать кому-нибудь пример неправильного доступа к памяти (пространство ядра пытается получить доступ к памяти пользовательского пространства, что приводит к ошибке).

Таким образом, я взял старый учебник как ПО C, важный часть:

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
   // [...]
}

Это вызывает cra sh в одном из моих тестов среды, что является ожидаемым поведением (я обращаюсь к буферу, который является переменной userspace без использования cpoy_*_user функционирует, таким образом, механизм защиты памяти задействован, и мой процесс убит.

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

Виртуальная машина, которая не взломала sh, сломана? Мой код на самом деле UB? Я что-то упустил?

После проверки в gdb я действительно получаю доступ к буферу переменная в GDB, которая не отображается ...:

gdb-peda$ hb dev_write
gdb-peda$ c
Thread 3 hit Breakpoint 1, dev_write () at /home/user/testMmap/ebbchar.c:144
144     static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset)
gdb-peda$ x/x $rip
0xffffffffc010d000 <dev_write>: 0x0f
gdb-peda$ x/s buffer
0x56139a0e5650: "a\n"
gdb-peda$ maintenance info sections
Exec file:
    `/home/max/prog/kgdb/remote/vmlinux', file type elf64-x86-64.
 [0]     0xffffffff81000000->0xffffffff81c04371 at 0x00200000: .text ALLOC LOAD RELOC READONLY CODE HAS_CONTENTS
 [1]     0xffffffff81c04374->0xffffffff81c0456c at 0x00e04374: .notes ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [2]     0xffffffff81c04570->0xffffffff81c08188 at 0x00e04570: __ex_table ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [3]     0xffffffff81e00000->0xffffffff82154f32 at 0x01000000: .rodata ALLOC LOAD RELOC DATA HAS_CONTENTS
 [4]     0xffffffff82154f40->0xffffffff82157af0 at 0x01354f40: .pci_fixup ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [5]     0xffffffff82157af0->0xffffffff82160b18 at 0x01357af0: __ksymtab ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [6]     0xffffffff82160b18->0xffffffff82169090 at 0x01360b18: __ksymtab_gpl ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [7]     0xffffffff82169090->0xffffffff8216d8a4 at 0x01369090: __kcrctab ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [8]     0xffffffff8216d8a4->0xffffffff82171b60 at 0x0136d8a4: __kcrctab_gpl ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [9]     0xffffffff82171b60->0xffffffff8219c23c at 0x01371b60: __ksymtab_strings ALLOC LOAD READONLY DATA HAS_CONTENTS
 [10]     0xffffffff8219c240->0xffffffff8219e478 at 0x0139c240: __param ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [11]     0xffffffff8219e478->0xffffffff8219f000 at 0x0139e478: __modver ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [12]     0xffffffff82200000->0xffffffff82349a00 at 0x01400000: .data ALLOC LOAD RELOC DATA HAS_CONTENTS
 [13]     0xffffffff82349a00->0xffffffff8235d2a8 at 0x01549a00: __bug_table ALLOC LOAD RELOC DATA HAS_CONTENTS in
 [14]     0xffffffff8235d2a8->0xffffffff824a7e28 at 0x0155d2a8: .orc_unwind_ip ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [15]     0xffffffff824a7e28->0xffffffff82697f68 at 0x016a7e28: .orc_unwind ALLOC LOAD READONLY DATA HAS_CONTENTS
 [16]     0xffffffff82697f68->0xffffffff826c807c at 0x01897f68: .orc_lookup ALLOC
 [17]     0xffffffff826c9000->0xffffffff826ca000 at 0x018c9000: .vvar ALLOC LOAD DATA HAS_CONTENTS
 [18]     0x00000000->0x0002b318 at 0x01a00000: .data..percpu ALLOC LOAD RELOC DATA HAS_CONTENTS
 [19]     0xffffffff826f6000->0xffffffff82764674 at 0x01af6000: .init.text ALLOC LOAD RELOC READONLY CODE HAS_CONTENTS
 [20]     0xffffffff82764674->0xffffffff8276500c at 0x01b64674: .altinstr_aux ALLOC LOAD RELOC READONLY CODE HAS_CONTENTS
 [21]     0xffffffff82766000->0xffffffff8284ccb0 at 0x01b66000: .init.data ALLOC LOAD RELOC DATA HAS_CONTENTS
 [22]     0xffffffff8284ccb0->0xffffffff8284ccd0 at 0x01c4ccb0: .x86_cpu_dev.init ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [23]     0xffffffff8284ccd0->0xffffffff8286ba8c at 0x01c4ccd0: .parainstructions ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [24]     0xffffffff8286ba90->0xffffffff828709bb at 0x01c6ba90: .altinstructions ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [25]     0xffffffff828709bb->0xffffffff82871f93 at 0x01c709bb: .altinstr_replacement ALLOC LOAD RELOC READONLY CODE HAS_CONTENTS
 [26]     0xffffffff82871f98->0xffffffff82872060 at 0x01c71f98: .iommu_table ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [27]     0xffffffff82872060->0xffffffff82872088 at 0x01c72060: .apicdrivers ALLOC LOAD RELOC DATA HAS_CONTENTS
 [28]     0xffffffff82872088->0xffffffff82872a81 at 0x01c72088: .exit.text ALLOC LOAD RELOC READONLY CODE HAS_CONTENTS
 [29]     0xffffffff82873000->0xffffffff8287a000 at 0x01c73000: .smp_locks ALLOC LOAD RELOC READONLY DATA HAS_CONTENTS
 [30]     0xffffffff8287a000->0xffffffff8287b000 at 0x01c7a000: .data_nosave ALLOC LOAD DATA HAS_CONTENTS
 [31]     0xffffffff8287b000->0xffffffff82a00000 at 0x01c7b000: .bss ALLOC
 [32]     0xffffffff82a00000->0xffffffff82a2c000 at 0x01c7b000: .brk ALLOC
 [33]     0x00000000->0x0000001c at 0x01c7b000: .comment READONLY HAS_CONTENTS
 [34]     0x00000000->0x000276c0 at 0x01c7b020: .debug_aranges RELOC READONLY HAS_CONTENTS
 [35]     0x00000000->0x0b2ba185 at 0x01ca26e0: .debug_info RELOC READONLY HAS_CONTENTS
 [36]     0x00000000->0x005172ad at 0x0cf5c865: .debug_abbrev READONLY HAS_CONTENTS
 [37]     0x00000000->0x012752a1 at 0x0d473b12: .debug_line RELOC READONLY HAS_CONTENTS
 [38]     0x00000000->0x0024d428 at 0x0e6e8db8: .debug_frame RELOC READONLY HAS_CONTENTS
 [39]     0x00000000->0x002d5379 at 0x0e9361e0: .debug_str READONLY HAS_CONTENTS
 [40]     0x00000000->0x00d028ae at 0x0ec0b559: .debug_loc RELOC READONLY HAS_CONTENTS
 [41]     0x00000000->0x00d46440 at 0x0f90de10: .debug_ranges RELOC READONLY HAS_CONTENTS
gdb-peda$ c
Continuing.
(finishes without crashing)

РЕДАКТИРОВАТЬ : Чтобы убедиться, что память не отображена, я попытался отобразить ее с помощью следующего g тестирование пространства пользователя по ответу @ Цыварева. Как ни странно, моя программа не обрабатывает sh даже в этом случае ...

#include<stdio.h>
#include<unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>

int main() {
    struct stat s;
    int in = open("aaa", O_RDONLY | O_RSYNC);
    fstat (in, &s);
    int size = s.st_size;
    char* ptr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, in, 0);
    int out = open("/dev/ebbchar",O_WRONLY);
    printf("Written = %d", write(out, ptr, size));
    close(in);
    close(out);
    return 0;
}

Примечание: полный код Po C можно найти ниже (контекст там )

/**
 * @file   ebbchar.c
 * @author Derek Molloy
 * @date   7 April 2015
 * @version 0.1
 * @brief   An introductory character driver to support the second article of my series on
 * Linux loadable kernel module (LKM) development. This module maps to /dev/ebbchar and
 * comes with a helper C program that can be run in Linux user space to communicate with
 * this the LKM.
 * @see http://www.derekmolloy.ie/ for a full description and follow-up descriptions.
 */

#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

// The prototype functions for the character driver -- must come before the struct definition
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 *);

/** @brief Devices are represented as file structure in the kernel. The file_operations structure from
 *  /linux/fs.h lists the callback functions that you wish to associated with your file operations
 *  using a C99 syntax structure. char devices usually implement open, read, write and release calls
 */
static struct file_operations fops =
{
   .open = dev_open,
   .read = dev_read,
   .write = dev_write,
   .release = dev_release,
};

/** @brief The LKM initialization function
 *  The static keyword restricts the visibility of the function to within this C file. The __init
 *  macro means that for a built-in driver (not a LKM) the function is only used at initialization
 *  time and that it can be discarded and its memory freed up after that point.
 *  @return returns 0 if successful
 */
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;
}

/** @brief The LKM cleanup function
 *  Similar to the initialization function, it is static. The __exit macro notifies that if this
 *  code is used for a built-in driver (not a LKM) that this function is not required.
 */
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");
}

/** @brief The device open function that is called each time the device is opened
 *  This will only increment the numberOpens counter in this case.
 *  @param inodep A pointer to an inode object (defined in linux/fs.h)
 *  @param filep A pointer to a file object (defined in linux/fs.h)
 */
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;
}

/** @brief This function is called whenever device is being read from user space i.e. data is
 *  being sent from the device to the user. In this case is uses the copy_to_user() function to
 *  send the buffer string to the user and captures any errors.
 *  @param filep A pointer to a file object (defined in linux/fs.h)
 *  @param buffer The pointer to the buffer to which this function writes the data
 *  @param len The length of the b
 *  @param offset The offset if required
 */
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)
   }
}

/** @brief This function is called whenever the device is being written to from user space i.e.
 *  data is sent to the device from the user. The data is copied to the message[] array in this
 *  LKM using the sprintf() function along with the length of the string.
 *  @param filep A pointer to a file object
 *  @param buffer The buffer to that contains the string to write to the device
 *  @param len The length of the array of data that is being passed in the const char buffer
 *  @param offset The offset if required
 */
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;
}

/** @brief The device release function that is called whenever the device is closed/released by
 *  the userspace program
 *  @param inodep A pointer to an inode object (defined in linux/fs.h)
 *  @param filep A pointer to a file object (defined in linux/fs.h)
 */
static int dev_release(struct inode *inodep, struct file *filep){
   printk(KERN_INFO "EBBChar: Device successfully closed\n");
   return 0;
}

/** @brief A module must use the module_init() module_exit() macros from linux/init.h, which
 *  identify the initialization function at insertion time and the cleanup function (as
 *  listed above)
 */
module_init(ebbchar_init);
module_exit(ebbchar_exit);

1 Ответ

1 голос
/ 16 января 2020

Прямой доступ из кода ядра к памяти пользовательского пространства равен плохо из-за двух возможных сценариев ios:

  1. Доступ память может не принадлежать процессу, потому что пользователь пробел с кодом передает системному вызову неверный указатель (по ошибке или намеренно).

  2. Доступная память может принадлежать процессу, но в настоящее время не отображается .

В обоих случаях будет вызвано сбой страницы , и поскольку ошибка вызвана кодом kernel , система обрабатывает эту ошибку как ошибку kernel .

Правильный доступ к памяти пространства пользователя - через copy_to_user / copy_from_user - обрабатывает эти сценарии ios изящно :

  1. Если память не принадлежит процессу пространства пользователя, a copy_*_user функция возвращает индикатор ошибки.

  2. Если память принадлежит процессу пространства пользователя, функция copy_*_user Ion проверяет, что он отображается во время доступа.

Итак, чтобы продемонстрировать, почему прямой доступ к памяти пространства пользователя плох, вы можете запустить выше сценария ios:

  1. Передайте недопустимый указатель (например, NULL) системному вызову write и обратите внимание, что ядро ​​дает сбой вместо возврата кода ошибки.

  2. Передайте правильный указатель на в настоящее время неотображаемая память для системного вызова write и обратите внимание, что ядро ​​падает, вместо правильного доступа к памяти.

    Не отображенный указатель можно получить, открыв некоторые (другие) файл и mmap -ing его содержимое: Для большинства файловых систем mmap возвращает изначально не отображенную память.

    Уточнение : успешный mmap() вызов возвращает указатель на память принадлежащий пользовательскому процессу . Но эта память может быть не отображенной в это время. Первый доступ к памяти (из кода пользовательского пространства) вызовет ошибку страницы, и во время нее память отобразится.

...