在下面的代码(char驱动程序的read
实现)中,是否有可能在wait_event_interruptible
期间更改MMU TLB条目,使得即使access_ok
成功__put_user
导致exception?
是否可以locking用户缓冲区,使其在请求期间保持有效?
在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; }
除非拥有mm_sem信号量,否则页表可随时更改(由同一进程的其他线程从不同处理器中取消映射页面,或从页面回收进程中逐出)。 你甚至不需要睡觉; 即使您禁用了抢占,也可能发生,只要TLB击落中断可以到达。 即使中断被禁用,也可能发生,如果您有SMP机器,有时候,即使没有明确的TLB刷新,也可以看到反映的页表更新。
access_ok()
只检查地址范围是否与内核空间不重叠 。 所以它不会告诉你任何有关页表项是否允许访问 – 但是其结果也不会改变,即使你阻止。 如果访问被拒绝, __put_user()
将返回-EFAULT
,它必须被传播到用户空间(即在这里用-EFAULT
出错)。
请注意, put_user()
和__put_user()
之间的唯一区别在于put_user()
也执行access_ok()
检查。 所以,如果你在一个循环中使用它,提前做一个access_ok()
并使用__put_user()
可能是正确的。