【问题标题】:How to decode arg pointer in ioctl() system call in Linux 2.6.29?如何在 Linux 2.6.29 的 ioctl() 系统调用中解码 arg 指针?
【发布时间】:2013-01-01 17:05:46
【问题描述】:

我想打印所有传递给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而不是指针,我如何获取内存内容?

【问题讨论】:

    标签: c linux linux-kernel system-calls ioctl


    【解决方案1】:

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

    比如看drivers/tty/tty_io.cTCSBRKPioctl的实现:

    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()get_user() 读取arg 指向的内存,但如果参数不是指针,则可能会失败。在通用 ioctl 系统调用中,您可能并不想拥有一个包含所有可能 ioctl 的巨大表。

    【讨论】:

      【解决方案2】:

      This document,也链接在this answer,应该更多地澄清这一点。一段有趣的摘录(我添加了粗体字)可能如下:

      在用户空间,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 参数以unsigned long 的形式传递,无论它是否由用户作为整数给出或指针。如果调用程序没有传递第三个参数,则驱动程序操作接收到的arg 值是未定义的。因为在额外参数上禁用了类型检查,所以如果将无效参数传递给 ioctl,编译器不会警告您,并且任何相关的错误都很难发现。

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

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

      问候。

      【讨论】:

        【解决方案3】:

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

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

        你只知道前两个参数是什么。根据this article

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

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

        【讨论】:

        • 通过查看代码,我发现它在 do_vfs_ioctl() 命令中被类型转换为 (int __user *)。我还在寻找,如果我能从这个 arg 中得到有用的东西。
        猜你喜欢
        • 1970-01-01
        • 2012-08-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-07-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多