c-如何在Linux 2.6.29中解码ioctl()系统调用中的arg指针



我想打印传递给linux系统调用的所有参数值。例如,在ioctl()的情况下,我有以下原型和print语句。

asmlinkage long our_sys_ioctl(unsigned int fd ,  unsigned int cmd , unsigned long arg)
{
printk ("fd=%u, cmd=%u and arg=%lu n ", fd, cmd, arg);
return original_call_ioctl(fd , cmd , arg);
}

据我所知,fd是驱动程序文件的文件描述符,cmd定义了驱动程序、ioctl编号、操作类型和参数大小。但我对arg参数感到困惑,它要么是指向内存的指针,要么只是大多数文档所称的立即值

通过使用这个arg参数,如果按照上面给出的unsigned long arg而不是指针传递,我如何获得内存内容?

ioctl的arg参数在通用vfs级别是不透明的。如何解释它取决于实际处理它的驱动程序或文件系统。因此,它可能是指向用户空间内存的指针,也可能是索引、标志等等。它甚至可能是未使用的,并且按惯例以0传递。

例如,看看drivers/tty/tty_io.c:中TCSBRKPioctl的实现

long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
//...
case TCSBRKP:   /* support for POSIX tcsendbreak() */
return send_break(tty, arg ? arg*100 : 250);

您可以查看ioctl_list(2)手册页,查看各种ioctl所采用的参数;该列表上具有CCD_ 12或其他非指针参数的所有条目都是其他示例。

所以你可以做一些类似的事情

void __user *argp = (void __user *) arg;

然后使用copy_from_user()get_user()读取arg指向的内存,但如果参数不是指针,则可能会失败。在通用ioctl系统调用中,您可能并不希望拥有一个包含所有可能ioctl的大表。

这份文件也链接在这个答案中,应该在部门中进一步澄清这一点。一个有趣的摘录(我添加了粗体文本)可能如下:

在用户空间中,ioctl系统调用具有以下原型:

int ioctl(int fd, unsigned long cmd, ...);

原型在Unix系统调用列表中脱颖而出,因为圆点通常将函数标记为具有可变数量的参数。然而,在实际系统中,系统调用实际上不能有可变数量的参数。系统调用必须有一个定义明确的原型,因为用户程序只能通过硬件"门"访问它们。因此,原型中的点表示不是可变数量的参数,而是单个可选参数,传统上被标识为char *argp。点就在那里,以防止编译期间进行类型检查第三个参数的实际性质取决于发出的特定控制命令(第二个参数)。有些命令不接受任何参数,有些命令接受整数值,有些命令则接受指向其他数据的指针。使用指针是将任意数据传递给ioctl调用的方法;则该设备能够与用户空间交换任意数量的数据。

ioctl调用的非结构化特性导致它在内核开发人员中失宠。每个ioctl命令本质上都是一个单独的、通常未记录的系统调用,并且无法以任何全面的方式审核这些调用octl通常是最简单、最直接的选择。

这意味着,如果没有对设备驱动程序约定/内部的深入了解,就无法理解如何将ioctl参数解释为外部观察者。从用户空间的角度来看,ioctl参数是非类型的,在内核空间中是松散类型的,因为它被处理为unsigned long只是为它保留空间。它是一个"纯"数字或适合unsigned long integer空间的任何位序列,可以用作(非常短的)字符串、,一个(小)结构——但要注意端序和特定于体系结构的大小——可以代表设备板载芯片的操作码,甚至可以作为浮点类型puning处理!

此外,这意味着很容易将不一致的数据(不仅是错误的数据,还有错误类型的错误数据!)传递给驱动程序,最终导致设备的未定义行为或用户空间内存损坏(例如,在读取ioctl中传递指向错误大小的结构的指针),从而使事情变得一团糟。

还有几行:

[…]cmd参数从用户传递而来,而可选的arg参数以unsigned long的形式传递,无论它是由用户作为整数还是指针给定的。如果调用程序没有传递第三个参数,那么驱动程序操作接收到的arg值是未定义的。由于对额外参数禁用了类型检查,因此如果将无效参数传递给ioctl,编译器将无法警告您,并且很难发现任何相关的错误。

无论如何,如果你想尝试对设备驱动程序ioctl调用进行"盲"审计,而不查看头文件和源文件,你可以尝试先将arg作为指针处理,使用copy_from_user(),失败的几率是立即值(或发生错误),然后可以尝试记录它的值来查看并尝试解释它(但为什么要反转ioctl而不是研究驱动程序代码呢?);一旦成功,就可以清除知识,读取和记录不同大小的内存以进行解码尝试(同样,只要有可用的源,就没有意义)。

更令人感兴趣的操作肯定是解码ioctl代码(cmd),因为它可以指向正确的方向来找到其数值所绑定的驱动程序-应该只有一个,如果应用ioctl定义的约定,无论如何,允许不同的驱动程序使用相同的魔术字符,因此,对包含"r"或"D"等的#define的所有内核源文件进行grep的正则表达式可以挑选出多个文件来检查ioctl定义,而与函数号匹配应该会挑选出一些或所有错误的文件,并且寻找正确的参数大小将完成搜索。

谨致问候。

请记住,ioctl的原型如下所示:

int ioctl(int fildes, unsigned long request, ...);

您只能确定前两个参数是什么。根据这篇文章:

其他参数是可选的,可能因一个设备上的ioctl实现不同而有所不同。据我所知,第三个论点总是存在的,我还没有找到超过三分之一的论点。第三个参数通常看起来是指向一个结构的指针。这允许在两个方向上传递任意数量的数据,数据由指针所指的结构定义,只需传递指针即可。

。。。但是,即使假设只存在第三个参数,您仍然不知道它是一个文字值还是一个指向结构的指针(没有请求到预期参数的显式映射)。

最新更新