copy_from_user()/__get_user() отлично работает в ioctl, но не удался за пределами ioctl

#c #linux-kernel #linux-device-driver

Вопрос:

В настоящее время я провожу эксперименты с модулем ядра.

Я написал функцию, которая принимает указатель на структуру (в пространстве пользователя) в качестве параметра, с целью скопировать эту структуру из пространства пользователя в пространство ядра; следовательно, мне нужно copy_from_user или __get_user .

Определение структуры довольно простое:

 struct A {
    int a;
};
 

Функция в моем модуле ядра направлена на получение значения a и возврат его значения, которое выглядит следующим образом (с двумя подходами):

 static int foo(struct A __user *arg)
{
    int num, ret; 

    if (!access_ok(VERIFY_WRITE, arg, sizeof(struct A)))
        return -EFAULT;
    
    /* approach1: directly copy the value from user space */
    ret = __get_user(num, (int __user *)amp;arg->a);
    if (ret) return -ENOMEM;

    /* approach2: allocate space for struct A, then copy the whole struct */
    struct A *tmp = kmalloc(sizeof(struct A), GFP_KERNEL);
    if (!tmp) return -ENOMEM;

    ret = copy_from_user(tmp, (const void __user *)arg, sizeof(struct A));
    if (ret) return -EFAULT;

    num = tmp->a;
    kfree(tmp);
    
    return num;
}
 

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

 long foo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct A __user *tmp_struct;
    int ret;
    ...

    switch (cmd) {
    case IOC_FOO:
        ret = foo((struct A __user *)arg);
        break;
    ...
    }
    
    ...
    return ret;
}
 

Однако, когда я перейду foo() к другой функции foo2() , она выйдет из строя в любом __get_user() или copy_from_user() . Псевдокод выглядит следующим образом:

 int foo2() 
{
    int val;
    ...
    struct A __user *addr = the address of struct A in user space
    val = foo(addr); /* this is where error occurrs */
    ...
}
 

Обратите внимание, что код является более простой версией моего эксперимента. foo2() вызывается через другой cmd in ioctl() , выданный тем же процессом. Я получил адрес структуры A — addr из пользовательского пространства, используя другой ioctl() cmd, который не имеет отношения к этому вопросу. Я уже проверил, что адрес структуры A в пользовательском пространстве правильный (путем печати адресов как в пользовательском пространстве, так и в пространстве ядра), что приводит меня в замешательство — почему действительный адрес пользовательского пространства приведет к ошибке в copy_from_user() или __get_user() ?

Почему foo() работает, ioctl() но не работает foo2() ?

Любая идея будет оценена по достоинству.

Комментарии:

1. Откуда foo2 звонят? Вы уверены, что это системный вызов, выполненный тем же процессом?

2. @NateEldredge: foo2 вызывается из другого командного ioctl пункта , выданного тем же процессом.

3. @EthanL. можете ли вы показать, как foo2() это называлось ?

4. @Khaled: foo2() был вызван ioctl, который запускает поток ядра. Внутри потока он сначала получает адрес struct A в пользовательском пространстве, а затем пытается скопировать всю структуру с помощью copy_from_user() . Я проверил, что адрес struct A в пользовательском пространстве, передаваемый ядру, правильный. Я знаю, что то, что я сделал, может не иметь никакого смысла, как я уже упоминал, я просто играю с простым модулем и провожу с ним некоторые небольшие эксперименты. Вот почему я также хотел бы протестировать интерфейс kthread, так как я никогда раньше не пытался создать поток ядра.

Ответ №1:

«который запускает поток ядра. Внутри темы» ой, это твоя ошибка. Вы можете вызвать копирование от/к пользователю только из потока, который выдал системный вызов или ошибку или иным образом попал в пространство ядра из пользовательского пространства. В некотором смысле, это тот же поток пользовательского пространства, только в пространстве ядра, поэтому вызовы работают. Из нового потока ядра вы больше не находитесь в этом потоке и не связаны с этим конкретным процессом пользовательского пространства, поэтому он не знает.

Тебе повезло, что это не удалось. В некоторых случаях это могло быть связано с init и стереть память init, что привело к панике.