LWN:对io_uring中能做的操作进行限制!

io_uring子系统已引入一组由用户配置的限制,旨在解决安全问题,特别是当一个进程将ringbuffer传递给另一个较不可信的进程时。新增的限制包括可执行操作的白名单、仅使用预注册文件描述符的能力等。

关注了就能看到更多这么棒的文章哦~

Operations restrictions for io_uring

By Jonathan Corbet
July 15, 2020
From: https://lwn.net/Articles/826053/
Translation benefits from DeepL

io_uring子系统的存在时间才刚刚一年。它是在2019年5月合入到5.1内核的。最初是作为一种更好的从用户空间执行异步I/O的方式而添加的。随着时间的推移,它已经获得了许多特性和支持功能,而不仅仅是做数据搬移而已了。它尚未具备的能力之一,是内核层面的安全机制,除了那些kernel已经为底层系统调用提供的安全机制以外。不过,这种情况可能会有所改变,因为Stefano Garzarella的一组新patch为io_uring添加了一组user-configurable restrictions。

人们可以从它的名字中了解,io_uring是一个基于内核和用户空间的共享机制的ring buffer,允许用户空间向内核来提交各种操作(operation)。此外还有一个ring buffer,里面装的是这些操作的结果。每一个操作都可以看做是一个系统调用的另一种表达方式;每个操作可以读写buffer、打开文件、通过网络发送消息、或者要求进行其他的一些操作。操作可以根据之前一个操作是否成功完成来作为前提条件。简而言之,送入内核的操作流(operation stream)可以看做是一种指定要求内核应该异步执行某些程序的描述性语言。

io_uring执行的操作会在内核内来触发调用那些相应系统调用的代码。例如,一个IORING_OP_READV操作,最后会和readv()系统调用走到同样的地方。这部分代码会使用当初创建ring的进程的credential来执行正常的权限检查。因此,假如没有bug的话,一个进程不能用io_uring来做那些无法直接调用系统调用可以做的事情。不过有个例外,就是seccomp() filters不适用于io_uring。到目前为止,这个工作模型对于io_uring来说工作得很好,不过现在看来,有一个使用场景会需要进行多一点控制。

具体来说,如果一个进程想要创建一个ring,并将其交给另一个不太受信任的进程,会发生什么?例如,如果能够使用io_uring的话,来自virtualized guest内部的I/O操作或许可以大大加速。这种I/O操作目前通常是使用Virtio机制来实现的,涉及到一定量的数据复制和上下文切换,这些开销在使用io_uring时候都可以避免。hypervisor程序可以创建guest系统里所需要的那些文件描述符,这些文件描述符是对应特定的外设或者是某个网络连接的,然后让Guest系统直接对ring操作来进行后续处理。

这个想法有一个问题,guest系统会能够执行io_uring所支持的所有操作。大家还记得,创建之后的ring会保留着创建者的credential,在这种场景里就是hypervisor。把这样一个ring给guest系统的话,将导致其可以访问hypervisor打开的其他文件描述符,或者可以用hypervisor的权限来打开新文件。这会让期待virtualization来作为安全屏障的用户感到非常失望。

根据Garzarella的说法,这个问题的一个解决方案就是允许为每个ring来注册一组限制条件。他为此增加了一个新的opcode(IORING_REGISTER_RESTRICTIONS)。可以支持添加几种类型的restriction:

  • IORING_RESTRICTION_REGISTER_OP

提供了在这个ring中可以执行的操作的注册操作列表。注册的操作会在ring中添加文件描述符和buffer,并优化它们在后续操作中的使用。换句话说,这些都是对ring本身的设置操作,实际上并不进行什么 I/O操作。

  • IORING_RESTRICTION_SQE_OP

这个ring中允许的操作(也就是实际的系统调用),也是以列表的形式提供的。这在代码中被称为 "白名单",在补丁进入主线之前,这个术语极有可能会被改掉。任何没有出现在这个列表中的操作都将在这个ring上被禁止。

  • IORING_RESTRICTION_FIXED_FILES_ONLY

如果应用了这个限制,那么在操作中只能使用之前在ring中注册过的文件描述符。换句话说,它可以用来限制一个ring只能对特定的一组已知文件集合进行操作。

因此,上面的大多数 "限制 "实际上都是permission,它们规定了此ring上允许做的事情。其中,这里的allowlist方法将有助于防止将来(肯定会发生的情况)当新的操作添加到io_uring列表中时出现意外。这些restriction只要应用一次就好,之后只要这个ring还存在,它们就会固定生效。

最后还有一点工作,这是由io_uring维护者Jens Axboe针对之前的patch set提出的,就是新增一个flag (IORING_SETUP_R_DISABLED),可以在第一次创建ring时提供。如果设置了这个flag,那么这个ring在启动时会进入disabled状态。这是注册操作仍然会成功,但任何其他操作都会失败。这使得ring创建者可以执行必要的注册和添加限制,而不必担心其他线程已经开始使用ring进行I/O。等到注册阶段完成后,IORING_REGISTER_ENABLE_RINGS这个注册操作将完成ring的配置并启用所有这些(被允许的)操作。

这种限制机制似乎足以满足上文中描述的场景,即允许对一组特定的文件描述符进行访问限制。很有可能今后有人会在某个时候希望添加更复杂的策略机制,到时候为这些安全策略增加一个BPF hook的做法很可能是无法避免的。不过在近期来说,本提议中的限制机制很可能可以用来加快虚拟机或其他不可信环境中的I/O速度,这会是一个很有用的优化。

全文完

LWN文章遵循CC BY-SA 4.0许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注LWN深度文章以及开源社区的各种新近言论~

int battery_type_check(int *battery_type) { int value = 0; int ret = 0; int ret_value = 0; int battery_id = 0; int battery_type_check = 0; if (batt_id == NULL) { bm_debug("[battery_type_check]: batt_id iio channel is null []\n"); *battery_type = BAT_TYPE__ATL_4400mV; battery_id = 0; return battery_id; } ret = iio_read_channel_processed(batt_id, &ret_value); if (ret < 0) bm_debug( "[battery_type_check] read channel err = %d,\n", ret); bm_debug( "[battery_type_check]: ret = %d,ret_value[%d]\n", ret, ret_value); value = ret_value; if(is_fuelgauge_apply() == true) { switch(battery_id) case 0: if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; break; case 1: if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; break; case 2: if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; break; case 3: *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; break; default:【兼容原来的方案】 if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; } else if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; } else if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } printk(KERN_ERR "[battery_type_check]: adc_value[%d], battery_type[%d], g_fg_battery_id[%d]\n", value, *battery_type, battery_id); return battery_id; }
08-21
这段代码的主要功能是检测电池类型,通过读取电池的ADC(模数转换)值来判断使用的是哪种类型的电池。以下是代码的详细解释: ### 代码功能说明: 1. **变量定义**: - `value`:存储读取到的ADC值。 - `ret`:存储读取ADC值的返回值(用于判断是否读取成功)。 - `ret_value`:从ADC通道读取的原始值。 - `battery_id`:表示电池的ID。 - `battery_type_check`:用于判断电池类型的变量。 2. **空指针检查**: 如果 `batt_id` 为 NULL,则将电池类型设置为默认值 `BAT_TYPE__ATL_4400mV`,并将 `battery_id` 设置为 0。 3. **读取ADC值**: 使用 `iio_read_channel_processed` 函数读取ADC值,并进行错误检查。 4. **电池类型判断**: 根据 `battery_id` 的值,使用 `switch-case` 结构判断电池类型。每个 `case` 对应一个电池类型,并检查ADC值是否在该类型的范围内。 5. **默认情况处理**: 如果 `battery_id` 不在预设范围内,则进入 `default` 分支,尝试匹配其他电池类型的ADC范围。 6. **日志输出**: 使用 `printk` 输出调试信息,包括ADC值、电池类型和电池ID。 ### 问题分析: 1. **语法错误**: - `switch-case` 结构的语法不正确,`case` 分支应该直接跟在 `switch` 后面,而不是嵌套在 `if` 语句中。 - `default:` 后面的冒号应该是英文冒号 `:`,而不是中文冒号 `:`。 2. **逻辑错误**: - `battery_id` 变量在 `switch-case` 结构中被用作判断条件,但它的值在代码中没有被正确更新或传递。 3. **代码结构问题**: - `switch-case` 结构应该放在 `if(is_fuelgauge_apply() == true)` 条件之外,或者需要重新设计逻辑以确保正确执行。 ### 改进后的代码: ```c int battery_type_check(int *battery_type) { int value = 0; int ret = 0; int ret_value = 0; int battery_id = 0; if (batt_id == NULL) { bm_debug("[battery_type_check]: batt_id iio channel is null []\n"); *battery_type = BAT_TYPE__ATL_4400mV; battery_id = 0; return battery_id; } ret = iio_read_channel_processed(batt_id, &ret_value); if (ret < 0) bm_debug("[battery_type_check] read channel err = %d,\n", ret); bm_debug("[battery_type_check]: ret = %d, ret_value[%d]\n", ret, ret_value); value = ret_value; if (is_fuelgauge_apply() == true) { switch (battery_id) { case 0: if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; } break; case 1: if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; } break; case 2: if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; } break; case 3: *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; break; default: // 兼容原来的方案 if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; } else if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; } else if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } break; } } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } printk(KERN_ERR "[battery_type_check]: adc_value[%d], battery_type[%d], g_fg_battery_id[%d]\n", value, *battery_type, battery_id); return battery_id; } ``` ### 代码改进说明: 1. **语法修正**: - 修复了 `switch-case` 结构的语法错误,确保每个 `case` 分支正确嵌套在 `switch` 语句中。 - 修正了 `default` 分支的冒号符号。 2. **逻辑优化**: - 确保 `battery_id` 在 `switch-case` 结构中被正确使用,并根据ADC值更新电池类型。 3. **代码结构优化**: - 保持代码结构清晰,确保 `if(is_fuelgauge_apply() == true)` 条件下的逻辑正确执行。 ### 相关问题: 1. 如何在Linux内核中实现ADC读取? 2. 如何设计电池类型的ADC阈值? 3. 如何调试和优化电池类型检测逻辑? 4. 如何处理电池类型检测中的异常情况?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值