对于struct file_operations中ioctl消失的学习笔记

对于struct file_operations中ioctl消失的学习笔记

 

文章启发点:

ioctl从大内核锁变更为需要内核开发工程师自己加小锁.这点很多内核开发工程师可能忘掉. 从而导致可能的条件竞争漏洞存在. 从这点来挖掘toc2tou的漏洞是个思路.

转载原文如下:

很久都没有写驱动代码了,对于一些驱动相关的内核变化也没有怎么关心。这次重游《LDD3》获益良多,其值对于struct file_operations中ioctl的消失也让我长了不少见识。
当年看《LDD3》的时候已经注意到了书中对ioctl的评价不是很好:“ioctl调用的非结构化本质导致众多内核开发者倾向于放弃它。” ,而在这次阅读3.0代码的时候,这个成员在struct file_operations中早已消失了。这个激起了我学习的兴趣,以下是对这个ioctl的学习小结:

1、消失的确切时间
ioctl的消失到底是从哪个版本开始的?网上给出的时间是2.6.36开始。网上就是这么说,但是自己必须找到代码中的证据。于是我通过git搜索主线内核代码,找到的删除ioctl的那个提交:

  1. commit b19dd42faf413b4705d4adb38521e82d73fa4249
  2. Author: Arnd Bergmann<arnd@arndb.de>
  3. Date: Sun Jul 4 00:15:10 2010 +0200
  4. bkl: Remove locked .ioctl file operation
  5. The last user is gone, so we can safely remove this
  6. Signed-off-by: Arnd Bergmann<arnd@arndb.de>
  7. Cc: John Kacur<jkacur@redhat.com>
  8. Cc: Al Viro<viro@zeniv.linux.org.uk>
  9. Cc: Thomas Gleixner<tglx@linutronix.de>
  10. Signed-off-by: Frederic Weisbecker<fweisbec@gmail.com>

好不容易找到了这个提交,好的,这样就可以确定消失的时间了:

  1. git tag –contains b19dd42
  2. v2.6.36
  3. v2.6.36-rc1
  4. v2.6.36-rc2
  5. v2.6.36-rc3
  6. v2.6.36-rc4
  7. v2.6.36-rc5
  8. v2.6.36-rc6
  9. v2.6.36-rc7
  10. v2.6.36-rc8
  11. ……以下省略ooxx行

可以证明ioctl消失的版本是v2.6.35到v2.6.36-rc1间,于是我导出了v2.6.35到v2.6.36-rc1的补丁,果真在其中!
git diff v2.6.35..v2.6.36-rc1 > ../temp.patch

补丁过大不易上传,请自行生成。

2、消失的原因

简单的概括:这次ioctl的消失,并不是要把ioctl清理出去,而是要逐步的清理大内核锁(BKL)。

这个让ioctl消失的过渡期长达5年,从2005年开始内核黑客就开始替换ioctl了。具体的原因lwn.net中有一篇很好的文章:The new way of ioctl()。我将他翻译了一下:ioctl()的新方法(必看)

当然,顺便了解一下大内核锁也是很有必要的:转载好文:《大内核锁将何去何从》

3、ioctl的替代者

对于原来的ioctl,其实可以叫做locked ioctl。这个其实是相对于他的替代方法来讲的。我们来看看2.6.35以前在struct file_operations中有关ioctl的成员:

  1. /*
  2.  * NOTE:
  3.  * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl
  4.  * can be called without the big kernel lock held in all filesystems.
  5.  */
  6. struct file_operations {
  7.     struct module *owner;
  8.     loff_t (*llseek) (struct file *, loff_t, int);
  9.     ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
  10.     ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
  11.     ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
  12.     ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
  13.     int (*readdir) (struct file *, void *, filldir_t);
  14.     unsigned int (*poll) (struct file *, struct poll_table_struct *);
  15.     int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
  16.     long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
  17.     long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
  18.     int (*mmap) (struct file *, struct vm_area_struct *);
  19.     int (*open) (struct inode *, struct file *);
  20.     int (*flush) (struct file *, fl_owner_t id);
  21.     int (*release) (struct inode *, struct file *);
  22.     int (*fsync) (struct file *, struct dentry *, int datasync);
  23.     int (*aio_fsync) (struct kiocb *, int datasync);
  24.     int (*fasync) (int, struct file *, int);
  25.     int (*lock) (struct file *, int, struct file_lock *);
  26.     ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
  27.     unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
  28.     int (*check_flags)(int);
  29.     int (*flock) (struct file *, int, struct file_lock *);
  30.     ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
  31.     ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
  32.     int (*setlease)(struct file *, long, struct file_lock **);
  33. };

这个结构体其实是在过渡期的结构体,unlocked_ioctl就是ioctl的替代者。对于新的驱动,不要再使用ioctl了,而是使用unlocked_ioctl。

4、调用ioctl与unlocked_ioctl在内核代码上的不同

   其实ioctl与unlocked_ioctl所对应的系统调用都是ioctl。但是在应用层调用ioctl的时候,对于我们实现ioctl或者unlocked_ioctl有什么不同呢?这里我们可以追溯一下ioctl系统调用代码的执行过程,这里我简单的写出这个系统调用对于设备驱动(一般是设备驱动使用ioctl)的执行顺序:(fs/ioctl.c)

 SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)—>do_vfs_ioctl—> vfs_ioctl

ioctl与unlocked_ioctl的区别就体现在了这个vfs_ioctl中,我们先来看ioctl被删除前的函数:

  1. /**
  2.  * vfs_ioctl – call filesystem specific ioctl methods
  3.  * @filp:    open file to invoke ioctl method on
  4.  * @cmd:    ioctl command to execute
  5.  * @arg:    command-specific argument for ioctl
  6.  *
  7.  * Invokes filesystem specific ->unlocked_ioctl, if one exists; otherwise
  8.  * invokes filesystem specific ->ioctl method. If neither method exists,
  9.  * returns -ENOTTY.
  10.  *
  11.  * Returns 0 on success, -errno on error.
  12.  */
  13. static long vfs_ioctl(struct file *filp, unsigned int cmd,
  14.          unsigned long arg)
  15. {
  16.     int error = -ENOTTY;
  17.     if (!filp->f_op)
  18.         goto out;
  19.     if (filp->f_op->unlocked_ioctl) {
  20.         error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
  21.         if (error == -ENOIOCTLCMD)
  22.             error = -EINVAL;
  23.         goto out;
  24.     } else if (filp->f_op->ioctl) {
  25.         lock_kernel();
  26.         error = filp->f_op->ioctl(filp->f_path.dentry->d_inode,
  27.                      filp, cmd, arg);
  28.         unlock_kernel();
  29.     }
  30.  out:
  31.     return error;
  32. }

从这个函数中我们可以看出:

  1. ioctl是受到大内核锁保护的,而unlocked_ioctl是直接执行的。
  2. unlocked_ioctl优先级高于ioctl,如果存在unlocked_ioctl,则执行unlocked_ioctl,否则才执行ioctl。这个优先级的产生明显是为了过渡。

而在ioctl被删除后,vfs_ioctl函数也做了相应的改变(Linux-3.0):

  1. /**
  2.  * vfs_ioctl – call filesystem specific ioctl methods
  3.  * @filp:    open file to invoke ioctl method on
  4.  * @cmd:    ioctl command to execute
  5.  * @arg:    command-specific argument for ioctl
  6.  *
  7.  * Invokes filesystem specific ->unlocked_ioctl, if one exists; otherwise
  8.  * returns -ENOTTY.
  9.  *
  10.  * Returns 0 on success, -errno on error.
  11.  */
  12. static long vfs_ioctl(struct file *filp, unsigned int cmd,
  13.          unsigned long arg)
  14. {
  15.     int error = -ENOTTY;
  16.     if (!filp->f_op || !filp->f_op->unlocked_ioctl)
  17.         goto out;
  18.     error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
  19.     if (error == -ENOIOCTLCMD)
  20.         error = -EINVAL;
  21.  out:
  22.     return error;
  23. }

5、在驱动编程时的注意事项

    • 在注册文件操作方法的结构体

struct file_operations的时候原先的.ioctl=OOXX;替换为 .unlocked_ioctl=OOXX;但是要注意ioctl和unlocked_ioctl的定义有一点不同:unlocked_ioctl少了一个inode参数。但是如果方法中真的需要其中的数据,可以通过filp->f_dentry->d_inode获得。

  • 由于失去了大内核锁的保护,所以必须unlocked_ioctl方法中自行实现锁机制,以保证不会在操作设备的时候(特别在SMP系统中)产生竞态。(也就实现了用小锁替换大锁)

 

 

file_operations结构体详细分析

http://www.linuxidc.com/Linux/2011-09/43530.htm

struct module *owner

第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针.
这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为
THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.这个宏比较复杂,在进行简单学习操作的时候,一般初始化为THIS_MODULE。
loff_t (*llseek) (struct file * filp , loff_t  p,  int  orig);
(指针参数filp为进行读取信息的目标文件结构体指针;参数 p 为文件定位的目标偏移量;参数orig为对文件定位
的起始地址,这个值可以为文件开头(SEEK_SET,0,当前位置(SEEK_CUR,1),文件末尾(SEEK_END,2))
llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值.
loff_t 参数是一个”long offset”, 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示.
如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在”file 结构” 一节中描述).

ssize_t (*read) (struct file * filp, char __user * buffer, size_t    size , loff_t *  p);
(指针参数 filp 为进行读取信息的目标文件,指针参数buffer 为对应放置信息的缓冲区(即用户空间内存地址),
参数size为要读取的信息长度,参数 p 为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值)
这个函数用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL(“Invalid argument”) 失败.
一个非负返回值代表了成功读取的字节数( 返回值是一个 “signed size” 类型, 常常是目标平台本地的整数类型).

ssize_t (*aio_read)(struct kiocb *  , char __user *  buffer, size_t  size ,  loff_t   p);
可以看出,这个函数的第一、三个参数和本结构体中的read()函数的第一、三个参数是不同 的,
异步读写的第三个参数直接传递值,而同步读写的第三个参数传递的是指针,因为AIO从来不需要改变文件的位置。
异步读写的第一个参数为指向kiocb结构体的指针,而同步读写的第一参数为指向file结构体的指针,每一个I/O请求都对应一个kiocb结构体);
初始化一个异步读 — 可能在函数返回前不结束的读操作.如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).
(有关linux异步I/O,可以参考有关的资料,《linux设备驱动开发详解》中给出了详细的解答)

ssize_t (*write) (struct file *  filp, const char __user *   buffer, size_t  count, loff_t * ppos);
(参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度,
ppos为当前的偏移位置,这个值通常是用来判断写文件是否越界)
发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
(注:这个操作和上面的对文件进行读的操作均为阻塞操作)

ssize_t (*aio_write)(struct kiocb *, const char __user *  buffer, size_t  count, loff_t * ppos);
初始化设备上的一个异步写.参数类型同aio_read()函数;

int (*readdir) (struct file *  filp, void *, filldir_t);
对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.

unsigned int (*poll) (struct file *, struct poll_table_struct *);
(这是一个设备驱动中的轮询函数,第一个参数为file结构指针,第二个为轮询表指针)
这个函数返回设备资源的可获取状态,即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏的位“或”结果。
每个宏都表明设备的一种状态,如:POLLIN(定义为0x0001)意味着设备可以无阻塞的读,POLLOUT(定义为0x0004)意味着设备可以无阻塞的写。
(poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞.
poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能.
如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写.
(这里通常将设备看作一个文件进行相关的操作,而轮询操作的取值直接关系到设备的响应情况,可以是阻塞操作结果,同时也可以是非阻塞操作结果)

int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
(inode 和 filp 指针是对应应用程序传递的文件描述符 fd 的值, 和传递给 open 方法的相同参数.
cmd 参数从用户那里不改变地传下来, 并且可选的参数 arg 参数以一个 unsigned long 的形式传递, 不管它是否由用户给定为一个整数或一个指针.
如果调用程序不传递第 3 个参数, 被驱动操作收到的 arg 值是无定义的.
因为类型检查在这个额外参数上被关闭, 编译器不能警告你如果一个无效的参数被传递给 ioctl, 并且任何关联的错误将难以查找.)
ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表.
如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, “设备无这样的 ioctl”), 系统调用返回一个错误.

int (*mmap) (struct file *, struct vm_area_struct *);
mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.
(如果想对这个函数有个彻底的了解,那么请看有关“进程地址空间”介绍的书籍)

int (*open) (struct inode * inode , struct file *  filp ) ;
(inode 为文件节点,这个节点只有一个,无论用户打开多少个文件,都只是对应着一个inode结构;
但是filp就不同,只要打开一个文件,就对应着一个file结构体,file结构体通常用来追踪文件在运行时的状态信息)
尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
与open()函数对应的是release()函数。

int (*flush) (struct file *);
flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作.
这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用;
SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.

int (*release) (struct inode *, struct file *);
release ()函数当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数:
void release(struct inode inode,struct file *file),release函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等。
在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.

int(*synch)(struct file *,struct dentry *,int datasync);
刷新待处理的数据,允许进程把所有的脏缓冲区刷新到磁盘。
int (*aio_fsync)(struct kiocb *, int);
这是 fsync 方法的异步版本.所谓的fsync方法是一个系统调用函数。系统调用fsync
把文件所指定的文件的所有脏缓冲区写到磁盘中(如果需要,还包括存有索引节点的缓冲区)。
相应的服务例程获得文件对象的地址,并随后调用fsync方法。通常这个方法以调用函数__writeback_single_inode()结束,
这个函数把与被选中的索引节点相关的脏页和索引节点本身都写回磁盘。

int (*fasync) (int, struct file *, int);
这个函数是系统支持异步通知的设备驱动,下面是这个函数的模板:
static int ***_fasync(int fd,struct file *filp,int mode)
{
struct ***_dev * dev=filp->private_data;
return fasync_helper(fd,filp,mode,&dev->async_queue);//第四个参数为 fasync_struct结构体指针的指针。
//这个函数是用来处理FASYNC标志的函数。(FASYNC:表示兼容BSD的fcntl同步操作)当这个标志改变时,驱动程序中的fasync()函数将得到执行。
}
此操作用来通知设备它的 FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述.
这个成员可以是NULL 如果驱动不支持异步通知.

int (*lock) (struct file *, int, struct file_lock *);
lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.

ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
这些方法实现发散/汇聚读和写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作;
这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为 NULL, read 和 write 方法被调用( 可能多于一次 ).

ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
这个方法实现 sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个.
例如, 它被一个需要发送文件内容到一个网络连接的 web 服务器使用. 设备驱动常常使 sendfile 为 NULL.

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动实际上不实现 sendpage.

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中.
这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为 NULL.[10]

int (*check_flags)(int)
这个方法允许模块检查传递给 fnctl(F_SETFL…) 调用的标志.

int (*dir_notify)(struct file *, unsigned long);
这个方法在应用程序使用 fcntl 来请求目录改变通知时调用. 只对文件系统有用; 驱动不需要实现 dir_notify.

一般情况下,进行设备驱动程序的设计只是比较注重下面的几个方法:
struct file_operations ***_ops={
.owner =  THIS_MODULE,
.llseek =  ***_llseek,
.read =  ***_read,
.write =  ***_write,
.ioctl =  ***_ioctl,
.open =  ***_open,
.release = ***_release,
};

 

How to generate gcc debug symbol outside the build target?

How to generate gcc debug symbol outside the build target?

1. how to strip elf.

2. how to separate debug information from elf files.

3. how to use debug information in gdb(or ida pro).

ida pro : file -> load file -> DBG file

4. how to merge debug information into elf files.

How debuggers work: Part 3 – Debugging information

About debugging information.

How to find functions, variables and line number based on debugging information(dwarf)

process infomation get within kernel

linux get process name from pid within kernel

  unsigned int pid = task_pid_nr(current);

 unsigned char * comm= current->comm;

printk(KERN_INFO “Simon:pid=%u,comm=%s\n,pid,comm);

How to get current process’s UID and EUID in Linux Kernel 4.2?

http://stackoverflow.com/questions/39229639/how-to-get-current-processs-uid-and-euid-in-linux-kernel-4-2

unsigned int uid = current_uid();

unsigned int euid = current_euid();

 unsigned int suid = current_suid();

unsigned int gid = current_gid();

Get time within kernel :

unsigned int get_kernel_time_sec()

{

        struct timeval ctv;

        do_gettimeofday(&ctv);

        return (unsigned int)ctv.tv_usec;

}// And then you can create a rand function based on time. 

unsigned int rand(unsigned int limit)

{

        unsigned int c_tv = get_kernel_time_sec();

        if(limit==0)

        {

                return c_tv;

        }

        else

        {

                return c_tv%limit;

        }

}

#include <linux/cred.h> // current_uid & current_euid

#include <linux/time.h> // current_kernel_time

#include <linux/types.h> //timespec

#include <linux/timer.h>

#include <linux/sched.h> // task_pid_nr    current->comm;