kptr_restrict 向用户空间内核中的指针(/proc/kallsyms-modules显示value全部为0)--http://blog.csdn.net/gatieme/article/details/78311841

This commit is contained in:
gatieme
2021-04-11 20:51:54 +08:00
parent bff272cdfd
commit 749bb4a796
6 changed files with 1383 additions and 9 deletions
@@ -90,26 +90,40 @@ When kptr_restrict is set to (2), kernel pointers printed using
>注意 `kptr_restrict` 对内核中很多地址和符号表的信息导出都有影响, 比如 `/proc/modules` 等.
#3 kptr_restrict的设计
# 3 kptr_restrict的设计
-------
| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 |
|:----:|:----:|:---:|:----:|:---------:|:----:|
| 2020/09/17 | Dan Rosenberg <drosenberg@vsecurity.com> | [kptr_restrict for hiding kernel pointers from unprivileged users](https://lore.kernel.org/patchwork/cover/229987) | 引入 kptr_restrict 限制都内核指针的读取 | v7 ☑ 2.6.38-rc1| [v7 PatchWork](https://lore.kernel.org/patchwork/cover/228326)<br>*-*-*-*-*-*-*-* <br>[v7 PatchWork](https://lore.kernel.org/patchwork/cover/229987)<br>*-*-*-*-*-*-*-* <br>[commit 455cd5ab305c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=455cd5ab305c90ffc422dd2e0fb634730942b257) |
| 2010/12/23 | Dan Rosenberg <drosenberg@vsecurity.com> | [kptr_restrict for hiding kernel pointers from unprivileged users](https://lore.kernel.org/patchwork/cover/229987) | 引入 kptr_restrict 限制都内核指针的读取 | v7 ☑ 2.6.38-rc1| [v7 PatchWork](https://lore.kernel.org/patchwork/cover/228326)<br>*-*-*-*-*-*-*-* <br>[v7 PatchWork](https://lore.kernel.org/patchwork/cover/229987)<br>*-*-*-*-*-*-*-* <br>[commit 455cd5ab305c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=455cd5ab305c90ffc422dd2e0fb634730942b257) |
| 2017/11/19 | Tobin C. Harding <me@tobin.cc> | [hash addresses printed with %p](https://lore.kernel.org/patchwork/cover/856356) | 内核中 %p 打印的地址会暴露内核态地址信息, 是极其不安全的, 因此限制 %p 的打印信息, 它将打印一个散列值, 并不是实际的地址. 如果想要打印实际地址, 需要显式指定 %px. | v11 ☑ 4.15-rc2 | [PatchWork](https://lore.kernel.org/patchwork/cover/856356)<br>*-*-*-*-*-*-*-* <br>[关键 commit 57e734423add](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=57e734423adda83f3b05505875343284efe3b39c) |
| 2019/04/17 | Petr Mladek <pmladek@suse.com> | [vsprintf: Prevent silent crashes and consolidate error handling](https://lore.kernel.org/patchwork/cover/1063193) | 此处填写补丁描述 | v7 ☑ 5.2-rc1 | [PatchWork](https://lore.kernel.org/patchwork/cover/1063193)<br>*-*-*-*-*-*-*-* <br>[commit 6eea242f9bcd](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6eea242f9bcdf828bb56334d8ee5c7cb466e4bcd) |
| 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")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ef0010a30935de4e0211cbc7bdffc30446cdee9b) |
| 2018/02/16 | Andy Shevchenko <andriy.shevchenko@linux.intel.com> | [lib/vsprintf: Deduplicate pointer_string()](https://lore.kernel.org/patchwork/patch/888079) | 简单的重构, 删除了一些重复的判断, 逻辑没有任何修正 | [PatchWork](https://lore.kernel.org/patchwork/cover/888079)<br>*-*-*-*-*-*-*-* <br>[关键 commit 496a9a5f3806](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=496a9a5f3806d58b14ae06b390d5b1ffa26e9f9a) |
| 2019/04/17 | Petr Mladek <pmladek@suse.com> | [hvsprintf: Prevent silent crashes and consolidate error handling](https://lore.kernel.org/patchwork/cover/1063203) | 修复上面补丁引入的一个小问题, 限制 restricted_pointer 中 kptr_restrict 为 0 时, 输出散列地址. | v7 ☑ 5.2-rc1 | [PatchWork](https://lore.kernel.org/patchwork/cover/1063193)<br>*-*-*-*-*-*-*-* <br>[关键 commit 1ac2f9789c4b](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1ac2f9789c4b76ad749870c25ffae0cbcd1f510f) |
| 2020/02/19 | Ilya Dryomov <idryomov@gmail.com> | [vsprintf: don't obfuscate NULL and error pointers](https://lore.kernel.org/patchwork/patch/1196790) | 同样的, 之前 NULL 指针和错误指针的输出也很混乱, 进行了归一化. | v2 ☑ 5.7-rc7 | [PatchWork](https://lore.kernel.org/patchwork/cover/1196790)<br>*-*-*-*-*-*-*-* <br>[关键 commit 7bd57fbc4a4d](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7bd57fbc4a4ddedc664cad0bbced1b469e24e921) |
## 3.1 v4.14 之前设计
## 3.1 v4.14 之前设计
-------
4.x 的内核中 `kptr_restrict` 在内核 [`commit 455cd5ab305c ("kptr_restrict for hiding kernel pointers from unprivileged users")`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=455cd5ab305c90ffc422dd2e0fb634730942b257), 具体实现源码位于 [`lib/vsprintf.c, line 1708`](http://elixir.free-electrons.com/linux/v4.13.9/source/lib/vsprintf.c#L1708)
linux 2.6.38 中 [`commit 455cd5ab305c ("kptr_restrict for hiding kernel pointers from unprivileged users")`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=455cd5ab305c90ffc422dd2e0fb634730942b257) 引入了 `kptr_restrict` 用来限制用户态读取内核指针的显示.
![v4.14 之前设计](./kptr_restrict_for_4.13.png)
具体处理 kptr_restrict 实现在 pointer 函数中, 该函数用于处理指针格式的 fmt 输出, 源码位于 [`lib/vsprintf.c, v4.13.9, line 1794`](http://elixir.free-electrons.com/linux/v4.13.9/source/lib/vsprintf.c#L1794), 自 2.6.x 到 4.14 之前的版本这段流程没有太大的改动.
```cpp
// https://elixir.bootlin.com/linux/v4.13.9/source/lib/vsprintf.c#L1708
// 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:
@@ -151,9 +165,29 @@ When kptr_restrict is set to (2), kernel pointers printed using
break;
}
break;
// ......
}
```
## 3.2 v5.2 之前设计
可见, 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](https://lore.kernel.org/patchwork/cover/229987) | 引入 kptr_restrict 限制都内核指针的读取 | v7 ☑ 2.6.38-rc1| [v7 PatchWork](https://lore.kernel.org/patchwork/cover/228326)<br>*-*-*-*-*-*-*-* <br>[v7 PatchWork](https://lore.kernel.org/patchwork/cover/229987)<br>*-*-*-*-*-*-*-* <br>[commit 455cd5ab305c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=455cd5ab305c90ffc422dd2e0fb634730942b257) |
## 3.2 v4.15 开始的设计
-------
### 3.2.1 printk 地址散列化
-------
```CPP
@@ -161,9 +195,225 @@ When kptr_restrict is set to (2), kernel pointers printed using
```
## 3.3 v5.2 之后设计
在 linux 4.15 中 [commit 57e734423add ("vsprintf: refactor %pK code out of pointer()")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=57e734423adda83f3b05505875343284efe3b39c) 将 point 中对 `%pK`(kptr_restrict) 的处理都封装到了 restricted_pointer 函数中. 这个修改比较简单, 就不详细描述了. 想要详细了解的同学可以查看提交的 commit
这次修改所属的 patchset 最主要是为了限制 %p 直接暴露内核的地址. 在早期内核中, 我们可以通过 System.map 或者 vmlinux 获取内核符号的地址信息, 内核中也较多的使用 %p 输出一些调试信息, 帮助用户显示一些符号的地址. 但是随着内核不断的演进, 开发者们发现, 随意暴露内核的地址是十分不安全的.
内核实现了越老越多的安全特性, 比如[内核地址随机化](https://lore.kernel.org/patchwork/cover/639469)等等. 正常情况下, 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 输出的影响是什么呢?
首先我们看下实现
![kptr_restrict_for_5.1](./kptr_restrict_for_5.1.png)
```cpp
// 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")](https://lore.kernel.org/patchwork/cover/856356) 或者 [commit 57e734423add ("vsprintf: refactor %pK code out of pointer()")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=57e734423adda83f3b05505875343284efe3b39c).
, 可以直接查看 [`restricted_pointer 函数, v5.1, Line 1481`](https://elixir.bootlin.com/linux/v5.1/source/lib/vsprintf.c#L1481).
上面的提交是最早一版本实现, 后来 Linus 提交了 [commit ef0010a30935 ("vsprintf: don't use 'restricted_pointer()' when not restricting")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ef0010a30935de4e0211cbc7bdffc30446cdee9b).
```cpp
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](https://lore.kernel.org/patchwork/cover/856356) | 内核中 %p 打印的地址会暴露内核态地址信息, 是极其不安全的, 因此限制 %p 的打印信息, 它将打印一个散列值, 并不是实际的地址. 如果想要打印实际地址, 需要显式指定 %px. | v11 ☑ 4.15-rc2 | [PatchWork](https://lore.kernel.org/patchwork/cover/856356)<br>*-*-*-*-*-*-*-* <br>[关键 commit 57e734423add](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=57e734423adda83f3b05505875343284efe3b39c) |
| 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")](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ef0010a30935de4e0211cbc7bdffc30446cdee9b) |
| 2019/04/17 | Petr Mladek <pmladek@suse.com> | [hvsprintf: Prevent silent crashes and consolidate error handling](https://lore.kernel.org/patchwork/cover/1063203) | 修复上面补丁引入的一个小问题, 限制 restricted_pointer 中 kptr_restrict 为 0 时, 输出散列地址. | v7 ☑ 5.2-rc1 | [PatchWork](https://lore.kernel.org/patchwork/cover/1063193)<br>*-*-*-*-*-*-*-* <br>[关键 commit 1ac2f9789c4b](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1ac2f9789c4b76ad749870c25ffae0cbcd1f510f) |
### 3.2.3 示例
-------
我们制作一个非常简单的用例, 分别用 %p, %pK, %px 打印一下子驱动中指针指向的数组的地址(指针的值)和指针本身的地址.
```cpp
#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* 的输出.
```cpp
#cat test.sh
#!/bin/bash
dmesg -c
for kptr in `seq 0 2`
do
echo $kptr > /proc/sys/kernel/kptr_restrict
KPTR=`cat /proc/sys/kernel/kptr_restrict`
echo "/proc/sys/kernel/kptr_restrict: $KPTR"
insmod ./kptr_restrict_test.ko
dmesg -c
done
```
![test kptr restrict](./kptr_restrict_3_test.png)
可以看到输出的内容与我的分析一致.
### 3.2.4 v5.7 的修正
-------
| 时间 | 作者 | 特性 | 描述 | 是否合入主线 | 链接 |
|:----:|:----:|:---:|:----:|:---------:|:----:|
| 2020/02/19 | Ilya Dryomov <idryomov@gmail.com> | [vsprintf: don't obfuscate NULL and error pointers](https://lore.kernel.org/patchwork/patch/1196790) | 同样的, 之前 NULL 指针和错误指针的输出也很混乱, 进行了归一化. | v2 ☑ 5.7-rc7 | [PatchWork](https://lore.kernel.org/patchwork/cover/1196790)<br>*-*-*-*-*-*-*-* <br>[关键 commit 7bd57fbc4a4d](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7bd57fbc4a4ddedc664cad0bbced1b469e24e921) |
进一步处理了 errr-ptr 和 NULL-ptr 的输出, 让在所有情况下, 显示都保持清晰, 一致.
```cpp
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
```
# 4 参考
-------
@@ -182,4 +432,4 @@ When kptr_restrict is set to (2), kernel pointers printed using
* 采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a><a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议</a>进行许可. 欢迎转载、使用、重新发布, 但务必保留文章署名[成坚gatieme](http://blog.csdn.net/gatieme) ( 包含链接: http://blog.csdn.net/gatieme ), 不得用于商业目的.
* 基于本文修改后的作品务必以相同的许可发布. 如有任何疑问请与我联系.
* 基于本文修改后的作品务必以相同的许可发布. 如有任何疑问, 请与我联系.
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@@ -0,0 +1,407 @@
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"type": "rectangle",
"version": 450,
"versionNonce": 552683567,
"isDeleted": false,
"id": "s_ygkG3FRv1ahkVIoxcay",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 414.25,
"y": -192.375,
"strokeColor": "#000000",
"backgroundColor": "#82c91e",
"width": 111.00000000000003,
"height": 36.00000000000002,
"seed": 1870171530,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"hC97PmiA9GpSyZPfCGdGv",
"3SxvWp5giaQFb8YaHyiTB",
"QN25t4_XOP7-HRxAuV7Y2",
"DhBK1onobgdtoteMzJ3RY",
"EVuGS3oXm7jYXCy8DsY2V",
"7-zXi5_a33f7uUIc7_j_9",
"OWrGRSWgDTVld8aCb9xUP",
"E-FoU4T3WQM9Nik2oNqPM",
"K5G4weFlvtjSkRkqHREH2",
"szZiIxpZJ5n5knUoyU-Tt"
]
},
{
"type": "text",
"version": 221,
"versionNonce": 67825519,
"isDeleted": false,
"id": "0Hga3UldDfoZg-zvVzbAY",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 424.25,
"y": -189.375,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 80,
"height": 27,
"seed": 1279813654,
"groupIds": [],
"strokeSharpness": "round",
"boundElementIds": [
"QN25t4_XOP7-HRxAuV7Y2",
"VGI_QGiy6and7H_GNB7Pc",
"nGXxbhYzARGmuHcQu1X-m",
"K5G4weFlvtjSkRkqHREH2",
"szZiIxpZJ5n5knUoyU-Tt",
"uUtqz6iqZjLmujW-TUWwG"
],
"fontSize": 20,
"fontFamily": 1,
"text": "vsnprintf",
"baseline": 20,
"textAlign": "left",
"verticalAlign": "top"
},
{
"type": "rectangle",
"version": 726,
"versionNonce": 733684961,
"isDeleted": false,
"id": "1yIXzrgDu_Y-3KB8OraLv",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 472.75,
"y": -99.875,
"strokeColor": "#000000",
"backgroundColor": "#15aabf",
"width": 296.99999999999994,
"height": 36.99999999999998,
"seed": 1222790218,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"hC97PmiA9GpSyZPfCGdGv",
"3SxvWp5giaQFb8YaHyiTB",
"K5G4weFlvtjSkRkqHREH2"
]
},
{
"type": "text",
"version": 563,
"versionNonce": 2063149455,
"isDeleted": false,
"id": "_tWQGmwtx0m11a2BVdQ3I",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 479.75,
"y": -94.875,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 284,
"height": 27,
"seed": 1718547798,
"groupIds": [],
"strokeSharpness": "round",
"boundElementIds": [
"EVuGS3oXm7jYXCy8DsY2V",
"_vUFgioABe191JEmcUjY2",
"z7YM5rJ0BgMPL8eaXkxtS",
"K5G4weFlvtjSkRkqHREH2",
"uDp2m25cLFz65IObaCZFH",
"HgJeBMwls67bGFfkIpmbO",
"szZiIxpZJ5n5knUoyU-Tt",
"uUtqz6iqZjLmujW-TUWwG"
],
"fontSize": 20,
"fontFamily": 1,
"text": " format_decode(fmt, &spec);",
"baseline": 20,
"textAlign": "left",
"verticalAlign": "top"
},
{
"type": "arrow",
"version": 251,
"versionNonce": 646848673,
"isDeleted": false,
"id": "YcCfbsKqX2VS1g1P_bC-m",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 440.25,
"y": -34.375,
"strokeColor": "#000000",
"backgroundColor": "#82c91e",
"width": 0,
"height": 0,
"seed": 874918666,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [],
"startBinding": null,
"endBinding": null,
"points": [
[
0,
0
],
[
0,
0
]
],
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow"
},
{
"type": "rectangle",
"version": 996,
"versionNonce": 1767536111,
"isDeleted": false,
"id": "8sqnW8in7QxJOaJy5OkQt",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 527.75,
"y": -4.875,
"strokeColor": "#000000",
"backgroundColor": "#7950f2",
"width": 94,
"height": 37.00000000000002,
"seed": 785504918,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"hC97PmiA9GpSyZPfCGdGv",
"3SxvWp5giaQFb8YaHyiTB"
]
},
{
"type": "text",
"version": 202,
"versionNonce": 1897962497,
"isDeleted": false,
"id": "-4u6DA6dGnCv6UuQmuSa_",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 536.75,
"y": -1.625,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 69,
"height": 27,
"seed": 1136607690,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [
"PEkbChJpyoyo7kyU5mOEJ",
"uDp2m25cLFz65IObaCZFH",
"HgJeBMwls67bGFfkIpmbO",
"6VA_aq8-ThvWPAmCT491Z",
"0wEPaK9Ydjv8-rscbi7La"
],
"fontSize": 20,
"fontFamily": 1,
"text": "pointer",
"baseline": 20,
"textAlign": "left",
"verticalAlign": "top"
},
{
"type": "arrow",
"version": 459,
"versionNonce": 1445234287,
"isDeleted": false,
"id": "HgJeBMwls67bGFfkIpmbO",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 489.75,
"y": -63.625000000000014,
"strokeColor": "#000000",
"backgroundColor": "#fa5252",
"width": 48.98127691785427,
"height": 74.60905669308931,
"seed": 1627182922,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElementIds": [],
"startBinding": {
"elementId": "_tWQGmwtx0m11a2BVdQ3I",
"focus": 0.8845543560970208,
"gap": 4.249999999999986
},
"endBinding": {
"elementId": "-4u6DA6dGnCv6UuQmuSa_",
"focus": -0.1623781111499215,
"gap": 9.018723082145698
},
"points": [
[
1.2453192294635667,
0
],
[
-11,
70.62633390758259
],
[
37.98127691785427,
74.60905669308931
]
],
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow"
},
{
"id": "s9oeJ5dFDmf6hA4KoMn9V",
"type": "text",
"x": 531,
"y": -33,
"width": 326,
"height": 22,
"angle": 0,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"strokeSharpness": "sharp",
"seed": 833389999,
"version": 142,
"versionNonce": 1331365423,
"isDeleted": false,
"boundElementIds": [
"HgJeBMwls67bGFfkIpmbO"
],
"text": "%p* 格式对应的 case 为 FORMAT_TYPE_PTR:",
"fontSize": 16,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top",
"baseline": 16
},
{
"id": "pXcS57X3P7KBcARXuSqkC",
"type": "text",
"x": 476,
"y": -126,
"width": 109,
"height": 22,
"angle": 0,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"strokeSharpness": "sharp",
"seed": 2084611503,
"version": 241,
"versionNonce": 744683585,
"isDeleted": false,
"boundElementIds": [
"HgJeBMwls67bGFfkIpmbO"
],
"text": "解析格式串 fmt",
"fontSize": 16,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top",
"baseline": 16
},
{
"id": "uUtqz6iqZjLmujW-TUWwG",
"type": "arrow",
"x": 435,
"y": -155,
"width": 41.00004383696145,
"height": 76.03127348853828,
"angle": 0,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"strokeSharpness": "sharp",
"seed": 988281345,
"version": 105,
"versionNonce": 1240548097,
"isDeleted": false,
"boundElementIds": null,
"points": [
[
-0.35555165893675955,
0
],
[
-4,
76.03127348853826
],
[
37.00004383696145,
76.03127348853826
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "0Hga3UldDfoZg-zvVzbAY",
"focus": 0.7037386910089231,
"gap": 7.375
},
"endBinding": {
"elementId": "_tWQGmwtx0m11a2BVdQ3I",
"focus": -0.1782424806324638,
"gap": 7.749956163038519
},
"startArrowhead": null,
"endArrowhead": "arrow"
}
],
"appState": {
"gridSize": null,
"viewBackgroundColor": "#ffffff"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB