CSDN GitHub
kptr_restrict 向用户空间内核中的指针(/proc/kallsyms-modules显示value全部为0) LinuxDeviceDrivers/study/debug/filesystem/procfs/kptr_restrict

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可, 转载请注明出处, 谢谢合作

因本人技术水平和知识面有限, 内容如有纰漏或者需要修正的地方, 欢迎大家指正, 也欢迎大家提供一些其他好的调试工具以供收录, 鄙人在此谢谢啦

1 /proc/kallsyms显示value全部为0


今天一个同事问我 cat /proc/kallsyms 显示 value 全部为 0. 我在手机端试了一下, 果然如此.

切换到 root 用户运行, 依然是 0. 感到十分奇怪, 因为内核发生 crash 或者打开 trace 的时候, 都是调用的 sprint_ symbol 来打印的. 为啥内核可以, 用户态 cat 就不行呢?

后来发现是系统为了保护这些符号地址泄露, 而用的一种保护手段, 从而使除 root 用户外的普通用户不能直接查看符号地址.

2 kptr_restrict 介绍


原因在于内核文件 kallsyms.c 中的显示符号地址命令中做了如下限制.

seq_printf(m, "%pK %c %s\n", (void *)iter->value, iter->type, iter->name);

只需要把其中的 %pK 换成 %p 就可以让普通用户查看符号地址了. 很多提权漏洞一般会用到此处的修改来获取符号地址

内核提供控制变量 /proc/sys/kernel/kptr_restrict 来进行修改. 从内核文档 Documentation/sysctl/kernel.txt 中可以看到 kptr_restrict 用于控制内核的一些输出打印.

Documentation/printk-formats.txt 有更加详细的描述, 除了我们平时遇到的一些打印格式之外, 还有一些比较特殊的格式(我以前没注意到).

==============================================================kptr_restrict:This toggle indicates whether restrictions are placed on
exposing kernel addresses via /proc and other interfaces.When kptr_restrict is set to (0), the default, there are no restrictions.When kptr_restrict is set to (1), kernel pointers printed using the %pK
format specifier will be replaced with 0's unless the user has CAP_SYSLOG
and effective user and group ids are equal to the real ids. This is
because %pK checks are done at read() time rather than open() time, so
if permissions are elevated between the open() and the read() (e.g via
a setuid binary) then %pK will not leak kernel pointers to unprivileged
users. Note, this is a temporary solution only. The correct long-term
solution is to do the permission checks at open() time. Consider removing
world read permissions from files that use %pK, and using dmesg_restrict
to protect against uses of %pK in dmesg(8) if leaking kernel pointer
values to unprivileged users is a concern.When kptr_restrict is set to (2), kernel pointers printed using
%pK will be replaced with 0's regardless of privileges.
==============================================================
kptr_restrict 权限描述
2 内核将符号地址打印为全0, root和普通用户都没有权限
1 root用户有权限读取, 普通用户没有权限
0 root和普通用户都可以读取

kptr_restrict 值为 2 时, 所有用户都无法读取内核符号地址.

kptr_restrict 值为 1 时, 普通用户都无法读取内核符号地址, root 用户可以查看.

kptr_restrict 值为 0 时, 所有用户都可以读取内核地址.

注意 kptr_restrict 对内核中很多地址和符号表的信息导出都有影响, 比如 /proc/modules 等.

3 kptr_restrict的设计


时间 作者 特性 描述 是否合入主线 链接
2010/12/23 Dan Rosenberg drosenberg@vsecurity.com kptr_restrict for hiding kernel pointers from unprivileged users 引入 kptr_restrict 限制都内核指针的读取 v7 ☑ 2.6.38-rc1 v7 PatchWork
-------
v7 PatchWork
-------
commit 455cd5ab305c
2017/11/19 Tobin C. Harding me@tobin.cc hash addresses printed with %p 内核中 %p 打印的地址会暴露内核态地址信息, 是极其不安全的, 因此限制 %p 的打印信息, 它将打印一个散列值, 并不是实际的地址. 如果想要打印实际地址, 需要显式指定 %px. v11 ☑ 4.15-rc2 PatchWork
-------
关键 commit 57e734423add
2017/11/29 Linus Torvalds torvalds@linux-foundation.org vsprintf: don’t use ‘restricted_pointer()’ when not restricting 如果发现 kptr_restrict 为 0, 则直接跳过 restricted_pointer() 流程. 这个补丁将影响 kptr_restrict 为 0 时, %pK 等同于 %p 输出散列地址. v1 4.15-rc2 commit ef0010a30935 (“vsprintf: don’t use ‘restricted_pointer()’ when not restricting”)
2018/02/16 Andy Shevchenko andriy.shevchenko@linux.intel.com lib/vsprintf: Deduplicate pointer_string() 简单的重构, 删除了一些重复的判断, 逻辑没有任何修正 PatchWork
-------
关键 commit 496a9a5f3806
2019/04/17 Petr Mladek pmladek@suse.com hvsprintf: Prevent silent crashes and consolidate error handling 修复上面补丁引入的一个小问题, 限制 restricted_pointer 中 kptr_restrict 为 0 时, 输出散列地址. v7 ☑ 5.2-rc1 PatchWork
-------
关键 commit 1ac2f9789c4b
2020/02/19 Ilya Dryomov idryomov@gmail.com vsprintf: don’t obfuscate NULL and error pointers 同样的, 之前 NULL 指针和错误指针的输出也很混乱, 进行了归一化. v2 ☑ 5.7-rc7 PatchWork
-------
关键 commit 7bd57fbc4a4d

3.1 v4.14 之前的设计


在 linux 2.6.38 中 commit 455cd5ab305c ("kptr_restrict for hiding kernel pointers from unprivileged users") 引入了 kptr_restrict 用来限制用户态读取内核指针的显示.

具体处理 kptr_restrict 实现在 pointer 函数中, 该函数用于处理指针格式的 fmt 输出, 源码位于 lib/vsprintf.c, v4.13.9, line 1794, 自 2.6.x 到 4.14 之前的版本这段流程没有太大的改动.

// https://elixir.bootlin.com/linux/v4.13.9/source/lib/vsprintf.c#L1794
static noinline_for_stack
char *pointer(const char *fmt, char *buf, char *end, void *ptr,struct printf_spec spec)
{// ......case 'K':switch (kptr_restrict) {case 0:/* Always print %pK values */break;case 1: {const struct cred *cred;/** kptr_restrict==1 cannot be used in IRQ context* because its test for CAP_SYSLOG would be meaningless.*/if (in_irq() || in_serving_softirq() || in_nmi()) {if (spec.field_width == -1)spec.field_width = default_width;return string(buf, end, "pK-error", spec);}/** Only print the real pointer value if the current* process has CAP_SYSLOG and is running with the* same credentials it started with. This is because* access to files is checked at open() time, but %pK* checks permission at read() time. We don't want to* leak pointer values if a binary opens a file using* %pK and then elevates privileges before reading it.*/cred = current_cred();if (!has_capability_noaudit(current, CAP_SYSLOG) ||!uid_eq(cred->euid, cred->uid) ||!gid_eq(cred->egid, cred->gid))ptr = NULL;break;}case 2:default:/* Always print 0's for %pK */ptr = NULL;break;}break;// ......
}

可见, kptr_restrict

  • 为 0 时, 未作任何处理, 直接输出, 这样对所有用户都没有限制.

  • 为 1 时, 中断上下文则不允许输出, 否则则校验了用户的权限.

  • 为 2 时, 将指针直接置 NULL, 这样所有用户都只能看到全 0.

整体思路还是比较清晰的. 想要详细了解实现全貌的, 可以直接查阅该特性合入时的 patchwork 或者 commit.

时间 作者 特性 描述 是否合入主线 链接
2010/12/23 Dan Rosenberg drosenberg@vsecurity.com kptr_restrict for hiding kernel pointers from unprivileged users 引入 kptr_restrict 限制都内核指针的读取 v7 ☑ 2.6.38-rc1 v7 PatchWork
-------
v7 PatchWork
-------
commit 455cd5ab305c

3.2 v4.15 开始的设计


3.2.1 printk 地址散列化


在 linux 4.15 中 commit 57e734423add (“vsprintf: refactor %pK code out of pointer()”) 将 point 中对 %pK(kptr_restrict) 的处理都封装到了 restricted_pointer 函数中. 这个修改比较简单, 就不详细描述了. 想要详细了解的同学可以查看提交的 commit

这次修改所属的 patchset 最主要是为了限制 %p 直接暴露内核的地址. 在早期内核中, 我们可以通过 System.map 或者 vmlinux 获取内核符号的地址信息, 内核中也较多的使用 %p 输出一些调试信息, 帮助用户显示一些符号的地址. 但是随着内核不断的演进, 开发者们发现, 随意暴露内核的地址是十分不安全的.

内核实现了越老越多的安全特性, 比如内核地址随机化等等. 正常情况下, kernel image 会按照 vmlinux 链接脚本中的链接地址去映射虚拟地址, 如果开启kaslr, 则会重新再映射一次, 映射到 链接地址 + offset的新地址上去. 如果 offset 每次开机都由 bootloader 随机生成, 那么每次开机后, kernel image最后映射的虚拟地址都不一样, 这就是内核地址随机化.

通过反汇编 vmlinux 以及读取 System.map 得不到内核中函数或者汇编语句的真正地址, 因为攻击者并不知道 kaslr 的 offset. 但是 %p 暴露内核地址将打破了这个限定. 攻击者通过 %p 打印中获取到的地址和 vmlinux 中该符号的地址进行比较, 就可以直接获取到 offset 偏移, 进而整个地址随机化机制将形同虚设.

最直接的办法就是将 %p 的打印全部处理掉.

  1. 没必要的从内核中删除掉

  2. 有必要但是可以限制的限制下(比如替换成 %pK).

  3. 特殊流程下, 要绕过限制直接打印实际地址的, 不做修改(比如 crash 流程输出 PANIC 日志的时候, 有必要输出实际地址.).

但是这些打印太多了, 改起来太麻烦了. 因此社区讨论后, 提出了一种新的方案.

  1. 限制 %p 的输出, 不再输出实际的地址, 而是输出一个散列化的地址.

  2. 想要在 crash 等流程输出实际地址时, 则使用 %px 输出内核符号的实际地址.

这样不用繁多的删动 %p 打印, 只需要限制 %p 的输出即可. 对于那些有必要输出实际地址的流程, 将 %p 替换为 %px, 这种路径通常是极少的, 改动起来也很少.

3.2.2 对 %pK 输出的影响


那么在 %p 的输出被散列的情况下, 对 %pK 输出的影响是什么呢?
首先我们看下实现

// https://elixir.bootlin.com/linux/v5.1/source/lib/vsprintf.c#L1481
static noinline_for_stack
char *restricted_pointer(char *buf, char *end, const void *ptr,struct printf_spec spec)
{switch (kptr_restrict) {case 0:/* Always print %pK values */break;case 1: {const struct cred *cred;/** kptr_restrict==1 cannot be used in IRQ context* because its test for CAP_SYSLOG would be meaningless.*/if (in_irq() || in_serving_softirq() || in_nmi()) {if (spec.field_width == -1)spec.field_width = 2 * sizeof(ptr);return string(buf, end, "pK-error", spec);}/** Only print the real pointer value if the current* process has CAP_SYSLOG and is running with the* same credentials it started with. This is because* access to files is checked at open() time, but %pK* checks permission at read() time. We don't want to* leak pointer values if a binary opens a file using* %pK and then elevates privileges before reading it.*/cred = current_cred();if (!has_capability_noaudit(current, CAP_SYSLOG) ||!uid_eq(cred->euid, cred->uid) ||!gid_eq(cred->egid, cred->gid))ptr = NULL;break;}case 2:default:/* Always print 0's for %pK */ptr = NULL;break;}return pointer_string(buf, end, ptr, spec);
}
kptr_restrict 实现 输出
0 不做任何处理, 直接 pointer_string 输出 不限制, 所有用户都可以看到实际地址
1 如果有权限(root 用户等), 直接 pointer_string 输出, 否则置 NULL 部分限制, 普通用户看到全 0, root 等用户看到实际地址
2 直接置 NULL 全限制, 任何用户都看到全 0

想要详细了解具体实现的, 可以直接查阅该特性合入时的 patchwork (“hash addresses printed with %p”) 或者 commit 57e734423add (“vsprintf: refactor %pK code out of pointer()”).
, 可以直接查看 restricted_pointer 函数, v5.1, Line 1481.

上面的提交是最早一版本实现, 后来 Linus 提交了 commit ef0010a30935 (“vsprintf: don’t use ‘restricted_pointer()’ when not restricting”).

static noinline_for_stack
char *pointer(const char *fmt, char *buf, char *end, void *ptr,struct printf_spec spec)
{// ......case 'K':if (!kptr_restrict)break;return restricted_pointer(buf, end, ptr, spec);// ....../* default is to _not_ leak addresses, hash before printing */return ptr_to_id(buf, end, ptr, spec);
}

这个提交在发现如果 kptr_restrict 为 0 的时候, 就不再走 restricted_pointer() 流程, 而是走标准的 %p 格式处理流程, 这个会打印散列的地址. 这本身是合理的, 但是 kptr_restrict 的影响发生了变化. 当然只是 kptr_restrict == 0 时, 等同于 %p, 输出的是散列地址.

kptr_restrict 实现 输出
0 不做任何处理, 直接跳过 restricted_pointer() 流程, 使用 ptr_to_id() 输出散列地址 限制, 所有用户都可以看到散列地址, 相当于 %p 输出.
1 如果有权限(root 用户等), 直接 pointer_string() 输出, 否则置 NULL 部分限制, 普通用户看到全 0, root 等用户看到实际地址
2 直接置 NULL 全限制, 任何用户都看到全 0

但是大家发现没发现, 上面的补丁其实有个问题, 存在一个空隙. 如果在处理 %pK 输出

  1. 在走到 pointer 的时候, 判断 kptr_restrict 不为 0, 走到 restricted_pointer() 流程.

  2. restricted_pointer() 流程中, 如果会继续检查 kptr_restrict 的值, 去做不同的处理.

这里就有间隙了. 前后两次判断 kptr_restrict 的过程中, 并没有加锁, 因此如果前后读取的过程中 kptr_restrict 的值被修改了, 那么行为会变的很奇怪. 因为在 restricted_pointer 流程中, 如果发现 kptr_restrict 为 0, 则是直接用 pointer_string() 输出了实际地址的. 而如果在 restricted_pointer 函数外面, 则等同于 %p, 输出的散列地址. 可见不同时机 %pK 的输出在 kptr_restrict == 0 的时候表现竟然不一样.

这有两种处理办法, 一种是加锁, 让这段流程成为临界区, 互斥执行. 但是这显然没必要. 另外一种方法最简单, 不保证互斥, 只是在 restricted_pointer 流程中如果发现用户期望不限制(kptr_restrict == 0), 则同样打印散列地址.

时间 作者 特性 描述 是否合入主线 链接
2017/11/19 Tobin C. Harding me@tobin.cc hash addresses printed with %p 内核中 %p 打印的地址会暴露内核态地址信息, 是极其不安全的, 因此限制 %p 的打印信息, 它将打印一个散列值, 并不是实际的地址. 如果想要打印实际地址, 需要显式指定 %px. v11 ☑ 4.15-rc2 PatchWork
-------
关键 commit 57e734423add
2017/11/29 Linus Torvalds torvalds@linux-foundation.org vsprintf: don’t use ‘restricted_pointer()’ when not restricting 如果发现 kptr_restrict 为 0, 则直接跳过 restricted_pointer() 流程. 这个补丁将影响 kptr_restrict 为 0 时, %pK 等同于 %p 输出散列地址. v1 4.15-rc2 commit ef0010a30935 (“vsprintf: don’t use ‘restricted_pointer()’ when not restricting”)
2019/04/17 Petr Mladek pmladek@suse.com hvsprintf: Prevent silent crashes and consolidate error handling 修复上面补丁引入的一个小问题, 限制 restricted_pointer 中 kptr_restrict 为 0 时, 输出散列地址. v7 ☑ 5.2-rc1 PatchWork
-------
关键 commit 1ac2f9789c4b

3.2.3 v5.7 的修正


时间 作者 特性 描述 是否合入主线 链接
2020/02/19 Ilya Dryomov idryomov@gmail.com vsprintf: don’t obfuscate NULL and error pointers 同样的, 之前 NULL 指针和错误指针的输出也很混乱, 进行了归一化. v2 ☑ 5.7-rc7 PatchWork
-------
关键 commit 7bd57fbc4a4d

进一步处理了 errr-ptr 和 NULL-ptr 的输出, 让在所有情况下, 显示都保持清晰, 一致.

                              ptr         error-ptr              NULL%p:            0000000001f8cc5b  fffffffffffffff2  0000000000000000%pK, kptr = 0: 0000000001f8cc5b  fffffffffffffff2  0000000000000000%px:           ffff888048c04020  fffffffffffffff2  0000000000000000%pK, kptr = 1: ffff888048c04020  fffffffffffffff2  0000000000000000%pK, kptr = 2: 0000000000000000  0000000000000000  0000000000000000

3.2.4 示例


我们制作一个非常简单的用例, 分别用 %p, %pK, %px 打印一下子驱动中指针指向的数组的地址(指针的值)和指针本身的地址.

#include <linux/init.h>
#include <linux/module.h>#include <linux/proc_fs.h>
#include <linux/seq_file.h>#include <linux/percpu.h>
#include <linux/sched.h>
#include <linux/version.h>#define SIZE 10
int array[SIZE];int *p_arr = array;
static int __init exam_seq_init(void)
{pr_info("%%p = %p, %%pK = %pK, %%px = %px\n", p_arr, p_arr, p_arr);pr_info("%%p = %p, %%pK = %pK, %%px = %px\n", &p_arr, &p_arr, &p_arr);return 0;
}static void __exit exam_seq_exit(void)
{}module_init(exam_seq_init);
module_exit(exam_seq_exit);
MODULE_LICENSE("GPL");

我们写个脚本, 分别在 kptr_restrict 为 0, 1, 2 时插入模块, 显示下 %p* 的输出.

#cat test.sh
#!/bin/bashdmesg -cfor kptr in `seq 0 2`
doecho $kptr > /proc/sys/kernel/kptr_restrictKPTR=`cat /proc/sys/kernel/kptr_restrict`echo "/proc/sys/kernel/kptr_restrict: $KPTR"insmod ./kptr_restrict_test.kodmesg -c
done

可以看到输出的内容与我的分析一致.

4 参考


Introducing Linux Kernel Symbols

Is there a way to set kptr_restrict to 0?

kptr_restrict for hiding kernel pointers from unprivileged users

Linux kallsyms 机制分析

  • 本作品/博文 ( AderStep-紫夜阑珊-青伶巷草 Copyright ©2013-2017 ), 由 成坚(gatieme) 创作,

  • 采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可. 欢迎转载、使用、重新发布, 但务必保留文章署名成坚gatieme ( 包含链接: http://blog.csdn.net/gatieme ), 不得用于商业目的.

  • 基于本文修改后的作品务必以相同的许可发布. 如有任何疑问,请与我联系.

kptr_restrict 向用户空间内核中的指针(/proc/kallsyms-modules显示value全部为0)相关推荐

  1. 【Linux 内核 内存管理】内存管理架构 ① ( 内存管理架构组成 | 用户空间 | 内核空间 | MMU 硬件 | Linux 内核架构层次 | Linux 系统调用接口 )

    文章目录 一.内存管理架构组成 ( 用户空间 | 内核空间 | MMU 硬件 ) 二.Linux 内核架构层次 三.Linux 系统调用接口 一.内存管理架构组成 ( 用户空间 | 内核空间 | MM ...

  2. Linux预备知识(二):进程空间地划分-用户空间/内核空间

    查看机器上栈大小命令 ulimit -a 或者 ulimit -s 大小不固定,可以用 ulimit -s 进行调整,默认一般为 8M ** 栈区(stack sagment)**:由操作系统自动分配 ...

  3. 【Linux 内核】Linux 内核体系架构 ( 硬件层面 | 内核空间 | 用户空间 | 内核态与用户态切换 | 系统调用 | 体系结构抽象层 )

    文章目录 一.Linux 内核体系架构 二.内核态与用户态切换 ( 系统调用层 ) 三.体系结构抽象层 一.Linux 内核体系架构 Linux 内核最初的源码不足一万行 , 当前的 Linux 内核 ...

  4. STC15分时内核中函数指针的使用问题

    2019独角兽企业重金招聘Python工程师标准>>> 基于前后台设计的系统随着功能的递增变得越来越难以维护, 所以决定为STC15F2K单片机编写一个基于时分的非抢占式内核,方便进 ...

  5. 跟踪 linux 内核调用_Linux用户和内核空间中的动态跟踪

    跟踪 linux 内核调用 您是否曾经遇到过这样的情况,即您意识到没有在代码中的某些点插入调试打印 ,所以现在您将不知道您的CPU是否命中了特定的代码行来执行,直到您重新编译该代码为止.调试语句? 不 ...

  6. 用户空间和内核空间通讯之【proc文件系统】

    今天我们介绍另一种用户内核空间通信的方法:proc文件系统. proc文件系统作为linux提供的一种虚拟文件系统并不占用实际外围存储空间,它仅存在于内存中,系统断电即消失.proc文件系统最开始的设 ...

  7. 关于用户空间和内核空间

    当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(内核态).在内核态下,CPU可执行任何指令.当进程在执行用户自己的代码时,则称其处于用户运行态(用户态).用户态不能访 ...

  8. linux内核那些事之用户空间管理

    内核主要数据结构 linux内核将用户空间抽象成struct vm_area_struct进行管理,每申请以个用户空间在内核中都会抽象成对应的vm_are_struct进行管理,同时为了区别不同进程的 ...

  9. Linux内核访问用户空间文件:get_fs()/set_fs()的使用

    测试环境:Ubuntu 14.04+Kernel 4.4.0-31 关键词:KERNEL_DS.USER_DS.get_fs().set_fs().addr_limit.access_ok. 参考代码 ...

最新文章

  1. MapReduce——shuffle
  2. ajax只能用get吗,基本的Ajax,但无法使用GET或POST方法
  3. mui框架之a标签无法跳转的问题
  4. Bug关于TP5.1与Swoole使用
  5. Hibernate sqlserver 的对象转成 Hibernate mysql 的对象时 需注意
  6. XNA中FPS统计刷新率频率类
  7. linux服务器怎么防,Linux服务器防攻击的各种方案
  8. dw中html颜色的设置颜色代码,Dreamweaver 如何编辑字体大小颜色
  9. 【CSS】学习笔记2 字体设置
  10. Minecraft Forge:如何下载,安装和使用Forge
  11. python目录操作_Python 简明教程 --- 25,Python 目录操作
  12. dbf转成excel_怎么样把dbf文件转换成excel/dbf转excle
  13. 电视信号服务器,基于Web服务器远程控制数字电视信号节目源再利用系统
  14. CSS盒模型完整介绍
  15. 【STC单片机学习】第九课:单片机按键使用
  16. 2021 Domain Adaptation(李宏毅
  17. 电脑硬盘锁怎么解除linux,硬盘锁了怎么办_硬盘锁怎么解除
  18. sap税码配置_SAP税务管辖码Tax Jurisditcion code功能(1)
  19. 说下类加载器与类加载?加载的信息放在哪个区域?
  20. 多线程——生产者消费者模型

热门文章

  1. 灵遁者油画作品《认真——沉默》
  2. 浅谈String的堆内存和栈内存
  3. dva使用及项目搭建
  4. React+dva多图片上传
  5. 欢迎段海华——我们开发者社区中文版的新版主!
  6. pdf java解析_用java如何解析pdf文件
  7. 如何使用计算机勾绘汇水面积,汇水面积怎么计算
  8. word-break 换行
  9. http:网易云音乐
  10. php jq 提交表单验证,jQuery EasyUI 表单 – 表单验证 | 菜鸟教程