2

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

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按上述方式传递而不是指针,我如何获取内存内容?

4

3 回答 3

5

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

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

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)man page看看各种ioctl取的参数;该列表中具有int或其他非指针参数的所有条目都是其他示例。

所以你可以做类似的事情

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

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

于 2013-01-18T09:06:55.640 回答
4

这份文件,也链接在这个答案中,应该更深入地阐明这一点。一个有趣的摘录(我添加了粗体文本)可能如下:

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

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

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

ioctl调用的非结构化特性使其在内核开发人员中失宠。每个ioctl命令本质上都是一个单独的、通常没有记录的系统调用,并且无法审计这些调用以任何全面的方式。也很难让非结构化 ioctl 参数在所有系统上都一样工作;例如,考虑在 32 位模式下运行用户空间进程的 64 位系统。结果,通过几乎任何其他方式实施杂项控制操作的压力很大。可能的替代方法包括将命令嵌入到数据流中(我们将在本章后面讨论这种方法)或使用虚拟文件系统,无论是 sysfs 还是特定于驱动程序的文件系统。(我们将在第 14 章讨论 sysfs。)然而,对于真正的设备操作来说,ioctl通常是最简单和最直接的选择。

这意味着没有任何方法可以理解如何将 ioctl 参数解释为外部观察者,而无需深入了解设备驱动程序约定/内部。从用户空间的角度来看, ioctl 参数是无类型的,并且在内核空间中以某种方式松散地键入,因为它被处理为unsigned long只是为其保留空间。它是一个“纯”数字或任何适合unsigned long integer空间的位序列,可以用作(非常短的)字符串、(小)char 数组、(小)结构 - 但要注意字节顺序和特定于体系结构大小 - 可以表示设备板载芯片的操作码,甚至可以通过类型处理作为浮点数处理!

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

还有几行:

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

无论如何,如果您想尝试对设备驱动程序 ioctl 调用进行“盲目”审核,而不查看头文件和源文件,则可以尝试先使用 copy_from_user() 将 arg 作为指针处理,如果失败,则可能是立即值(或发生错误),然后可以尝试记录其值以查看并尝试解释它(但为什么要反转 ioctl 而不是研究驱动程序代码?);在成功的情况下,在没有知识的情况下,可以读取并记录不同大小的内存以进行解码尝试(同样,只要有可用的资源,它们应该是毫无意义的)。

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

问候。

于 2013-08-22T18:13:07.950 回答
2

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

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

你只知道前两个参数是什么。根据这篇文章

附加参数是可选的,并且可能因一台设备上的 ioctl 实现而异于另一台设备上的实现。据我所知,总是存在第三个论点,而我还没有找到超过三分之一的论点。这第三个参数通常似乎是一个指向结构的指针。这允许在两个方向上传递任意数量的数据,数据由指针所指的结构定义,只需传递指针即可。

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

于 2013-01-18T02:07:52.483 回答