copy_to_user не работает с таблицами страниц ядра Linux

Я пишу системный вызов в ядре Linux, который дает виртуальный адрес и длинный указатель без знака, находит соответствующую запись таблицы страниц и затем копирует ее содержимое в длинный указатель без знака. Вот системный вызов:

SYSCALL_DEFINE2(readMMU, unsigned long, vaddr, unsigned long*, pte) {
    unsigned long* kernel_pte;
    unsigned char* page_table;
    struct task_struct *pid_task;
    pgd_t *pgd;
    pud_t *pud;
    pmd_t *pmd;
    pte_t *page_te;
    unsigned long n;


    kernel_pte = kmalloc(sizeof(unsigned long), GFP_KERNEL);

    if (copy_from_user(kernel_pte, pte, sizeof(unsigned long)) > 0) {
        printk("Error: copy from user returned more than 0\n");
        return -1;
    }

    //System call implementation in between here
    printk("Getting Task\n");

    pid_task = get_pid_task(find_get_pid(current->pid), PIDTYPE_PID);

    printk("Getting pgd\n");
    pgd = pgd_offset(pid_task->mm, vaddr);

    printk("Getting pud\n");
    pud = pud_offset(pgd, vaddr);

    printk("Getting pmd\n");
    pmd = pmd_offset(pud, vaddr);

    printk("Getting pte\n");
    page_te = pte_offset_kernel(pmd, vaddr);

    *kernel_pte = pte_val(*page_te);

    printk("Can we access pte?: %d\n", access_ok(VERIFY_WRITE, pte, sizeof(unsigned long)));
    printk("Can we acces kernel_pte?: %d\n", access_ok(VERIFY_READ, kernel_pte, sizeof(unsigned long)));

    if ((n = copy_to_user(pte, kernel_pte, sizeof pte)) > 0) {
        printk("Error: copy to user returned more than 0\n");
        printk("copy to user failed to copy this many bits: %ld\n", n);
        return -1;
    }

    kfree(kernel_pte);
    return 0;
}

Вот тестовая программа, вызывающая системный вызов:

int
main (int argc, char ** argv) {
    unsigned long vaddr;
    unsigned long *pte;
    vaddr = (size_t) malloc(sizeof(unsigned long));



    /* Print a friendly message */
    printf ("Hello from User Space!\n");



    /* Call our new system call */
    syscall (181, vaddr, pte);

    /* Exit the program */
    return 0;
}

В настоящее время вызов copy_to_user завершается ошибкой с возвращаемым значением 8, что означает, что он не скопировал ни один элемент kernel_pte в pte. Я проверил pte с помощью access_ok для VERIFY_WRITE, и он возвращается с 1. Однако acces_ok, вызванный kernel_pte с VERIFY_READ, возвращается с 0. Я не уверен, что именно это вызывает сбой copy_to_user, но смотрю на исходный код для copy_to_user похоже, он снова проверяет только указатель пользователя. Так что я немного не понимаю, почему звонок не работает.


person idavison    schedule 04.12.2015    source источник
comment
access_ok проверяет только то, что адрес не является адресом ядра. Для успеха copy_to_user адрес должен принадлежать адресному пространству текущего процесса. Вам необходимо проверить программу пользовательского пространства, которая передает адрес системному вызову.   -  person Tsyvarev    schedule 05.12.2015
comment
Мне кажется, что адресное пространство pte должно быть правильным, потому что оно было передано в системный вызов из тестовой программы.   -  person idavison    schedule 05.12.2015
comment
Вы не инициализируете pte в тестовой программе. Или вы должны объявить его unsigned long и передать его адрес системному вызову,   -  person Tsyvarev    schedule 05.12.2015
comment
Вот и все, спасибо! Для меня глупо это игнорировать.   -  person idavison    schedule 05.12.2015


Ответы (2)


Вы не инициализируете pte в тестовой программе. Или вы должны объявить его unsigned long и передать его адрес системному вызову Tsyvarev

person Community    schedule 16.08.2017

Это совершенно неверно. Чего вы пытаетесь достичь?

SYSCALL_DEFINE2(readMMU, unsigned long, vaddr, unsigned long*, pte) {

Фигурная скобка должна находиться на отдельной строке. readMMU - плохая репутация, несовместимая с другими системными вызовами. Звезда возле pte неуместна.

    unsigned long* kernel_pte;
    unsigned char* page_table;

Неуместные звезды. Вы уверены, что эти типы здесь вообще правильные?

    struct task_struct *pid_task;

Для чего это?

    pgd_t *pgd;
    pud_t *pud;
    pmd_t *pmd;
    pte_t *page_te;
    unsigned long n;


    kernel_pte = kmalloc(sizeof(unsigned long), GFP_KERNEL);

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

    if (copy_from_user(kernel_pte, pte, sizeof(unsigned long)) > 0) {
        printk("Error: copy from user returned more than 0\n");
        return -1;
    }

Откуда у вас эта проверка ошибок? Вы должны проверить! = 0.

    //System call implementation in between here
    printk("Getting Task\n");

Неправильно. Все printks должны иметь префиксы. Для меня вы можете просто использовать KERN_CRIT.

    pid_task = get_pid_task(find_get_pid(current->pid), PIDTYPE_PID);

Неправильно на нескольких уровнях. Прежде всего, должно быть очевидно, что ядро ​​уже имеет некоторый доступ к 'task_struct' исполняемого потока. Так получилось, что макрос, используемый для его получения, называется «текущий» и используется здесь для получения pid. Затем вы используете find_get_pid и get_pid_task без удержания rcu, что является ошибкой, о которой ядро ​​сообщило бы вам, если бы у вас была включена отладка. Наконец, get_pid_task увеличивает счетчик ссылок задачи, который вы утекаете, так как нигде не убываете. Обратите внимание, что задача, которую вы ищете, находится в «текущей», которую вы все равно используете.

Если это предполагается использовать позже для запроса состояния других потоков, это все равно неверно.

    printk("Getting pgd\n");
    pgd = pgd_offset(pid_task->mm, vaddr);

current определенно будет иметь допустимый мм на этом этапе, но если вы хотите проверить другие резьбы, вы должны сначала проверить правильность мм.

    printk("Getting pud\n");
    pud = pud_offset(pgd, vaddr);

    printk("Getting pmd\n");
    pmd = pmd_offset(pud, vaddr);

    printk("Getting pte\n");
    page_te = pte_offset_kernel(pmd, vaddr);

    *kernel_pte = pte_val(*page_te);

    printk("Can we access pte?: %d\n", access_ok(VERIFY_WRITE, pte, sizeof(unsigned long)));

Это разумно, но бесполезно (см. Ниже).

    printk("Can we acces kernel_pte?: %d\n", access_ok(VERIFY_READ, kernel_pte, sizeof(unsigned long)));

В чем смысл этой проверки? Мало того, что это не может быть успешным, это прямо неправильно.

    if ((n = copy_to_user(pte, kernel_pte, sizeof pte)) > 0) {

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

        printk("Error: copy to user returned more than 0\n");
        printk("copy to user failed to copy this many bits: %ld\n", n);
        return -1;
    }

    kfree(kernel_pte);
    return 0;
}

И пользовательское пространство:

int
main (int argc, char ** argv) {
    unsigned long vaddr;
    unsigned long *pte;
    vaddr = (size_t) malloc(sizeof(unsigned long));

Прежде всего, для чего это приведение size_t? Кроме того, вы включили stdlib.h? Что заставляет вас думать, что возвращаемое значение malloc не усечено, и, по сути, то, что вы получаете в vaddr, на самом деле то, в что вы не можете писать? Это определенно объяснило бы сбой ядра copy_to_user.

    /* Print a friendly message */
    printf ("Hello from User Space!\n");



    /* Call our new system call */
    syscall (181, vaddr, pte);

    /* Exit the program */
    return 0;
}
person employee of the month    schedule 07.12.2015