linux 内核、用户空间缓冲区、access_ok 和 wait 是否会创建竞争条件?
在以下代码(字符驱动程序的 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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
除非您持有 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 thatput_user()
performs anaccess_ok()
check as well. So if you're using it in a loop, doing a singleaccess_ok()
ahead of time and using__put_user()
is probably the right thing to do.