0

I’m writing a device driver. If someone calls the write operation I want it to be deferred (using tasklet or workqueue). The code should be something like that:

static ssize_t dev_write(struct file *filp, const char *buff, size_t len, loff_t *off) {

      packed_work *the_task;
      the_task = kzalloc(sizeof(packed_work), GFP_ATOMIC);
      if (the_task == NULL) {
         printk(KERN_ERR "%s: tasklet buffer allocation failure\n", MODNAME);
         return -1;
      }

      the_task->buffer = the_task;
      the_task->buff = buff;
      the_task->len = len;

      INIT_WORK(&(the_task->the_work), (void*)deferred_write);
      schedule_work(&the_task->the_work);

      return len;
}

void deferred_write(struct work_struct *data) {
    
      printk(“the text: %s\n”, container_of(data, packed_work, the_work)->buff);

      //copy_from_user(&(the_object->stream_content), container_of(data, packed_work, the_work)->buff, len);

      kfree((void*)container_of(data,packed_work,the_work));   

}

And the struct looks like this:

typedef struct _packed_work{
    void *buffer;
    const char *buff; 
    size_t len;
    struct work_struct the_work;
} packed_work;

The problem is that the kernel crashes. It crashes even before the copy_from_user (that’s why I commented it). In the deferred_write() I can print the length of the string but not the string itself. Is it a problem because the buffer is in the user space memory?

I know that, as a workaround, I can copy the user buffer in the task struct (using the copy_from_user() in the function write()) and then use the strcpy() in the deferred_write() function. But I really would like to use the copy_from_user() in deferred_write(). Is it possible? What can I do?

  • Please provide a [mcve]. There are too many undefined parts in the code you have shown to know where the problem is. – ryyker Apr 06 '22 at 12:22
  • What do you expect to happen if the user code has changed the values in the buffer before deferred_write runs? Or unmapped the buffer? Or if the process has exited? – user253751 Apr 06 '22 at 12:35
  • 1
    You can only use user addresses in the context of the user process. If you defer the write to a kernel thread, that user process is no longer mapped. – stark Apr 06 '22 at 13:02
  • @stark presumably it is possible to map the user process to do the access. The problem is that the memory contents may have changed. – user253751 Apr 06 '22 at 13:57
  • @user253751 you are right, I can't use the user buffer because it might change. So, I have to do the copy_from_user in the write function and save the buffer in the kernel space. Thanks! – Silvia di Luise Apr 06 '22 at 16:35
  • There are ways to do asynchronous I/O in Linux, but `write()` is not one of them. Look up POSIX AIO (man 7 aio), Linux native AIO (libaio), and Linux io_uring (liburing). – Ian Abbott Apr 07 '22 at 13:59

1 Answers1

2

Even if it is possible (and there is surely a way), the user process has probably changed the contents of the buffer in the time before deferred_write runs. Notice that user programs often allocate these buffers on the stack, so they get overwritten when the function that called write returns and calls other functions.

Even worse: the user process could have unmapped the buffer, or it could have exited.

So you should not delay reading the buffer. You should read the buffer inside the write call and not anywhere else.

user253751
  • 57,427
  • 7
  • 48
  • 90