Linux 内核模块加载过程

0x0

项目开发过程中,尝试加载一个内核模块时出错,查看系统日志,有以下错误信息:

  iscsi_sfnet: disagrees about version of symbol struct_module

经过调试发现,是因为这个内核模块与当前 Linux kernel 版本不一致,所以无法加载。更新版本后问题解决。

0x1

以下内容是在问题解决后对于内核模块加载过程的一些学习记录。

  1. 使用 insmod 或者 modprobe 加载内核模块(需要 root 权限),比如:
      insmod hello.ko,    或者
      modprobe hello      (需要通过 depmod 更新模块的依赖关系)
    

  2. 用户工具通过 sys_init_module() 这个系统调用陷入内核,来完成将模块加载到内核里去的动作

    1. 首先,会对这个模块进行各种检查,以确保对它的加载不会影响内核的运行,包括

      1. 确认文件的大小,比如最大不能超过 6MB、最小不能小于 ELF 头(大概几十个字节,与 CPU 位数有关)
      2. Application Binary Interface (ABI) 一致性检查
      3. vermagic 检查
      4. 确认是否为 rel 类型的文件,而不是 exe、so 或者其他
      5. 确认是否与当前运行的硬件类型匹配,比如 i386、arm 等等
      6. 确认 ELF 文件结构是否完整,比如 ELFMAG 是否匹配、等等

    2. 检查通过后开始加载,此处不关心这个过程,从略 …

  3. 浏览了 sys_init_module() 的实现,上述错误的原因是 ABI 一致性检查未通过

    1. 通常内核模块里有一个名为 __versions 的段,它的内容很简单,是编译模块时所用内核导出的部分符号以及其对应的 CRC 校验值
        13579bdf    struct_module
        2468ace0    printk
      

    2. 这个校验值是在一个特定的内核版本里计算出来的,用于区别不同版本的内核

      1. 安装内核源码包(*.rpm,从 kernel-*.spec 里可以看到由 Module.kabi 生成了 Module.symvers)或者内核编译后会在源码目录下生成 Module.symvers 文件,里面记录了所有导出符号及 CRC 校验值
      2. 编译内核模块时会将对应源码目录下的 struct_module、printk 或者其他一些导出符号及其 CRC 校验值链接到内核模块的 __versions 段里去
      3. 加载内核模块时,内核会将其与运行内核里的相应字段作比较,以此来保证 ABI 一致

    3. modprobe 有一个选项,--force-modversion,告诉内核忽略可能出现的 ABI 不一致,当然这样做很危险,可能发生内核崩溃

    4. 使用 objdump 查看 __versions 这个段里的内容,格式简单、可读,比如:
        $ objdump -s ext3.ko
        Contents of section __versions:
         0000 b2294749 00000000 73747275 63745f6d  .)GI....struct_m
         0010 6f64756c 65000000 00000000 00000000  odule...........
         0020 00000000 00000000 00000000 00000000  ................
         0030 00000000 00000000 00000000 00000000  ................
      

    5. 对比需要加载的内核模块和内核中任一可加载的内核模块的差异,也可以从系统日志里查看加载失败的原因

  4. vermagic 检查

    1. 加载内核模块时,也会检查 vermagic,它是一个可读的、用于表示内核版本的字符串

      1. 内核源码里有一个宏 VERMAGIC_STRING 里,表示了这份内核头文件的版本,比如 2.6.18-el5 SMP mod_unload gcc-4.1,其含义非常直观
      2. 内核模块里的 .modinfo 段,其中 vermagic 字段里记录了编译模块时所用内核源码的版本和编译器的信息;可以通过 objdump -s 的方式获取,也可以直接使用 modinfo 工具来查看,比如:
          $ modinfo ext3.ko
          filename:       ext3.ko
          license:        GPL
          description:  Second Extended Filesystem with journaling extensions
          author:         Remy Card, Stephen Tweedie, Andrew Morton, Andreas Dilger, Theodore Ts'o and others
          srcversion:     C06AB23BDA1C57EFB6501CF
          depends:        jbd
          vermagic:       2.6.18-el5 SMP mod_unload gcc-4.1
        

      3. 加载内核模块时,内核会将其与自己的版本作比较,保证内核版本的匹配

    2. modprobe 有一个选项,--force-vermagic,告诉内核忽略 vermagic 的不一致,当然这样做很危险

  5. 加载内核模块时有许多 ELF 格式的完整性检查,以此来过滤掉格式错误的文件、被 Hack 过的文件、等等

Read More: