linux 内核、用户空间缓冲区、access_ok 和 wait 是否会创建竞争条件?

发布于 2024-11-27 04:18:48 字数 1995 浏览 7 评论 0原文

在以下代码(字符驱动程序的 read 实现)中,MMU TLB 条目是否有可能在 wait_event_interruptible 期间更改,使得 __put_user即使 access_ok 成功也会导致异常?

是否可以锁定用户缓冲区,使其在请求期间保持有效?

wait_event_interruptible 返回后重复 access_ok 检查是否可以保证安全?

ssize_t mydriver_pkt_read( struct file* filp, char __user* const buff, size_t count, loff_t* offp )
{
  struct mydriver_pkt_private* priv;
  volatile unsigned short* iobase;
  unsigned next;
  char __user* p = buff;

  if (count <= 0) return -EINVAL;
  if (!access_ok(VERIFY_WRITE, buff, count)) return -EFAULT;

  priv = (struct mydriver_pkt_private*)filp->private_data;
  iobase = priv->iobase;

  next = priv->retained;
  if ((next & PKTBUF_FLAG_NOTEMPTY) == 0) {
    next = ioread16(iobase);
    if ((next & PKTBUF_FLAG_NOTEMPTY) == 0) { // no data, start blocking read
      iowrite16(1, iobase); // enable interrupts
      if (wait_event_interruptible(priv->wait_for_ringbuffer, (priv->retained & PKTBUF_FLAG_NOTEMPTY)))
          return -ERESTARTSYS;
      next = priv->retained;
    }
  }

  while (count > 0) {
    __put_user( (char)next, p );
    p++;
    count--;
    next = ioread16(iobase);
    if ((next & PKTBUF_FLAG_STARTPKT) || !(next & PKTBUF_FLAG_NOTEMPTY)) {
      priv->retained = next;
      return (p - buff);
    }
  }

  /* discard remainder of packet */
  do {
    next = ioread16(iobase);
  } while ((next & PKTBUF_FLAG_NOTEMPTY) && !(next & PKTBUF_FLAG_STARTPKT));
  priv->retained = next;
  return (p - buff);
}

独家开放码:

int mydriver_pkt_open( struct inode* inode, struct file* filp )
{
  struct mydriver_pkt_private* priv;

  priv = container_of(inode->i_cdev, struct mydriver_pkt_private, cdevnode);

  if (atomic_cmpxchg(&priv->inuse, 0, 1))
    return -EBUSY;

  nonseekable_open(inode, filp);

  filp->private_data = priv;
  return 0;
}

In the following code (the read implementation for a char driver), is it possible for MMU TLB entries to change during wait_event_interruptible, such that __put_user causes an exception even though access_ok succeeded?

Is it possible to lock the user buffer such that it remains valid for the duration of the request?

Would repeating the access_ok check after wait_event_interruptible returns make this safe?

ssize_t mydriver_pkt_read( struct file* filp, char __user* const buff, size_t count, loff_t* offp )
{
  struct mydriver_pkt_private* priv;
  volatile unsigned short* iobase;
  unsigned next;
  char __user* p = buff;

  if (count <= 0) return -EINVAL;
  if (!access_ok(VERIFY_WRITE, buff, count)) return -EFAULT;

  priv = (struct mydriver_pkt_private*)filp->private_data;
  iobase = priv->iobase;

  next = priv->retained;
  if ((next & PKTBUF_FLAG_NOTEMPTY) == 0) {
    next = ioread16(iobase);
    if ((next & PKTBUF_FLAG_NOTEMPTY) == 0) { // no data, start blocking read
      iowrite16(1, iobase); // enable interrupts
      if (wait_event_interruptible(priv->wait_for_ringbuffer, (priv->retained & PKTBUF_FLAG_NOTEMPTY)))
          return -ERESTARTSYS;
      next = priv->retained;
    }
  }

  while (count > 0) {
    __put_user( (char)next, p );
    p++;
    count--;
    next = ioread16(iobase);
    if ((next & PKTBUF_FLAG_STARTPKT) || !(next & PKTBUF_FLAG_NOTEMPTY)) {
      priv->retained = next;
      return (p - buff);
    }
  }

  /* discard remainder of packet */
  do {
    next = ioread16(iobase);
  } while ((next & PKTBUF_FLAG_NOTEMPTY) && !(next & PKTBUF_FLAG_STARTPKT));
  priv->retained = next;
  return (p - buff);
}

Exclusive open code:

int mydriver_pkt_open( struct inode* inode, struct file* filp )
{
  struct mydriver_pkt_private* priv;

  priv = container_of(inode->i_cdev, struct mydriver_pkt_private, cdevnode);

  if (atomic_cmpxchg(&priv->inuse, 0, 1))
    return -EBUSY;

  nonseekable_open(inode, filp);

  filp->private_data = priv;
  return 0;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

你不是我要的菜∠ 2024-12-04 04:18:48

除非您持有 mm_sem 信号量,否则页表可以随时更改(通过同一进程的其他线程从不同处理器取消映射页面,或者通过从页面回收进程中逐出)。你甚至不需要睡觉;即使您禁用了抢占,只要 TLB 关闭中断可以到达,这种情况也可能发生。即使中断被禁用,如果您有一台 SMP 机器,即使没有显式的 TLB 刷新,您有时也可以看到页表更新的反映。

access_ok() 仅检查地址范围不与内核空间重叠。因此,它不会告诉您有关页表条目是否允许访问的任何信息 - 但即使您阻止,其结果也不会改变。如果访问被拒绝,__put_user() 将返回 -EFAULT,它必须传播到用户空间(即,这里使用 -EFAULT 出错) 。

请注意,put_user()__put_user() 之间的唯一区别是 put_user() 执行 access_ok() > 也检查一下。因此,如果您在循环中使用它,提前执行一次 access_ok() 并使用 __put_user() 可能是正确的做法。

Unless you have the mm_sem semaphore held, page tables can change at any time (by other threads of the same process unmapping pages from a different processor, or by evictions from page reclaim processes). You don't even need to sleep; it can happen even if you have preemption disabled, as long as the TLB shootdown interrupt can arrive. And it can happen even if interrupts are disabled, if you have a SMP machine, as you can, sometimes, see page table updates reflected even without an explicit TLB flush.

access_ok() only checks that the range of addresses does not overlap with kernel space. So it doesn't tell you anything about whether the page table entries allow access - but its result also does not change, even if you block. If access is denied, __put_user() will return -EFAULT, which must be propagated to userspace (ie, error out here with -EFAULT).

Note that the only difference between put_user() and __put_user() is that put_user() performs an access_ok() check as well. So if you're using it in a loop, doing a single access_ok() ahead of time and using __put_user() is probably the right thing to do.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文