浅析vendor_init

本文深入探讨了Android系统中VENDOR_INIT机制的作用与工作原理,重点分析了其在SystemProperties属性加载、service启动及onsection事件触发中的应用。通过详细代码解读,揭示了vendor_initcontext如何与odm和vendor分区交互,以及它在SElinux安全模型下的权限控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

浅析 VENDOR INIT

Vic.LUO@TINNO.COM

 

https://source.android.google.cn/security/selinux/vendor-init

推荐先阅读一下以上Google官方文章

 

 

定义vendor_init context,从这里可以看出,在odm和vendor分区的文件相关操作,其和vendor_init的关联性比较大

 

 

使用场景:

1.SystemProperties 属性加载相关

开机过程在load system property的时候,其都会走到该函数load_properties_from_file,随后会调用到LoadProperties,其有如下代码

 

就是当prop文件是在vendor/odm分区的时候,系统会用vendor_init的context去设置该perperity,在HandlePropertySet 函数中,会调用如下代码做selinux检查

其中source_context就是vendor_init,target_context就是要设置的属性对应的secontext(定义在property_contexts中),最后调用selinux_check_access函数来检查vendor_init对该属性的set操作是否allow

所以如果通过如下方式添加或者修改属性值

PRODUCT_PROPERTY_OVERRIDES += persist.vendor.bluetooth.modem_nv_support=true

有可能在实际操作中出现通过adb shell getprop 获取不到该值,需要注意,此时有2种可能性。

①代码中功能生效,但是adb shell getprop获取不到

针对该情况,说明属性值已经被正确的设置到系统中,只是通过adb shell getprop不能获取,原因在于shell环境的secontext是u:r:shell:s0,如果没有加上对应的selinux policy,或者有些属性本身就对shell neverallow get,那么就读取不到,譬如上文提到的

type=1400 audit(0.0:35835): avc: denied { read } for name="u:object_r:vendor_default_prop:s0" dev="tmpfs" ino=16504 scontext=u:r:shell:s0 tcontext=u:object_r:vendor_default_prop:s0 tclass=file permissive=0

对于vendor_default_prop而言,vendor_init有设置权限,通过以下selinux 规则可以知道

set_prop(vendor_init, vendor_default_prop)

但是对于shell context而言,其没有明确声明权限读取,而SELINXU policy中就是 权限默认是不允许的,除非主动声明(allow subject object:class operation),否则都视为不允许,所以如果没有声明,就是不被允许,也就会出现上面log中描述的权限被拒绝。

②代码中功能不生效,adb shell getprop也获取不到

针对该情况,说明该属性值在开机过程中并没有被成功设置到property service中,这个时候就要抓到开机log,一般情况下,说明vendor_init 没有权限去设置该属性,那么此时就需要添加vendor_init 对该属性的set权限的对应规则了。

 

 

2.service相关

在init中,我们可以发现如下代码

所以这里可以预估到在service相关以及on section相关的流程中,vendor_init也会有相应作用。

 

这里我们先来看一下init进程中的InitializeSubcontexts,请注意该函数的返回值std::vector<Subcontext> subcontexts; 是Subcontext的vector,不是string类型。其中

注意下,使用的是c++ 11新特新 auto结合for来使用,这里就会从paths_and_secontexts的二维数组中取出来其中一行,其中的2个元素分别为path_prefix和secontext,然后通过

subcontexts.emplace_back(path_prefix, secontext);直接将path_prefix和secontext new成一个subcontext对象,然后加入到subcontexts这个vector集合中,这里的话有2个subcontext对应,需要注意subcontext对象

New的时候会执行for函数

看完该函数我们可以发现该函数主要做了以下事情

① 创建一对socket  socket_和subcontext_socket

② 在当前进程上下文(init进程)下执行fork操作,创建子进程

③ 在子进程中,重新设置进程的context,这里就是u:r:vendor_init:s0了(同时由于paths_and_secontexts是有2个,所以这里会创建2次,这样也就最终会有2个vendor_init,也就最终导致了系统有2个vendot_init context的init进程),并且传入参数(fd,secontext,init进程的路径,以及subcontext),重新执行init(vendor_init)进程

 

Init(vendor_init)进程中会执行以下code逻辑

将context(这里就是vendor_init)以及subcontext_socket和function_map作为变量传入SubcontextProcess,subcontext_socket保存到SubcontextProcess的init_fd_中,然后就执行死循环MainLoop,也就是2个子进程已经进入了死循环,等待命令来处理的状态,那么命令从何而来?也就是我们平时怎么切入到vendor_init的上下文环境

④上述是子进程的逻辑,init主进程fork之前,会先创建socket_和subcontext_socket一对sockerpair,可以猜想到,init与vendor_init之间的通讯,很有可能就是通过他们俩,socket_保存在subcontext对象的变量中,这里要注意,2个subcontext对象的上下文还是在init进程,而非vendor_init子进程中,SubcontextProcess是运行在vendor_init子进程上下文里。

 

以上讲述了vendor_init子进程创建的过程,接下来我们就讲和subcontext紧密关联的部分code

先来看service部分(system/core/init/service.cpp),在service ParseSection的环节中

会通过service初始化传入到onrestart变量中(onrestart是一个action变量

也就是该subcontext会和位于vendor/odm 的 rc文件中的service的onrestart操作相关联,其中vendor分区rc文件对应的vendor subcontext,odm分区对应rc文件对应odm subcontext。

当service某些原因发生了重启

ReapOneProcess->service->Reap(siginfo)->onrestart_.ExecuteAllCommands()->

command.InvokeFunc(subcontext_);

注意下invokefunc函数

其中subcontext_就是onrestart传入进去的subcontext,execute_in_subcontext_则是new command时候传入的

从代码得知,当前new command应该是在AddCommand中产生的

第二种情况传入了f的话,execute_in_subcontext_默认就是false,第一种情况,如果没有传入f的话,那么会在function_map_中去查找,其最终对应的是builtins.cpp中的

其中第一个代表command,第二,三2个数字代表可传入参数个数的范围,第四个代表就是表示execute_in_subcontext_,第五个表示最终command执行的函数。

 

我们再回到InvokeFunc

①如果当前subcontext_为空,也就是action对应的rc文件是在system分区,那么直接直接调用RunBuiltinFunction(func_, args_, kInitContext);就是在init进程(u:r:init:s0)中执行该函数。

②如果subcontext_,但是execute_in_subcontext_为空(也就是function_map_中那些第四个参数为false的情况),这里会先执行subcontext->ExpandArgs,请注意ExpandArgs和③中一样,也会最终调用TransmitMessage,然后会在vendor_init(SubcontextProcess)中执行,但是通过代码可以发现,其实SubcontextProcess的ExpandArgs函数并没有真正执行什么,只是做了一些strings的检查和转换,随后在SubcontextProcess返回后,又在init中执行

RunBuiltinFunction(func_, *expanded_args, subcontext->context()) ,这里我们可以看到在init进程中执行这些函数用到subcontext->context()的地方其实很少,当前只有do_restorecon_recursive,do_installkey和do_init_user0会应到args.context

 

③subcontext_不为null和execute_in_subcontext_为true(就是command对应的rc为odm or vendor,并且在function_map中的参数为true),这种情况下会执行到

subcontext->Execute(args_)  ---->subcontext->TransmitMessage

这里可以看到,文章一开始讲的socket_在这里排上用场了,这里就直接将args参数相关的command通过socket_传输,通讯方就是socketpair的另一个,也就是传入到SubcontextProcess中的init_fd_,这样SubcontextProcess子进程MainLoop就会从ReadMessage中唤醒过来

然后解析参数,这里subcontext_command.command_case()的类型为SubcontextCommand::kExecuteCommand,原因是在subcontext->Execute中,以下代码

mutable_execute_command会通过set_has_execute_command()将subcontext_command类型设置为kExecuteCommand(这些代码都是protobuf自动生成的,在out目录),所以这里执行Runcommand

在该函数中,可以看到熟悉的init中的执行代码,RunBuiltinFunction,也就是在vendor_init中执行command,可以发现,vendor_init中设置属性(command中setprop)的操作通过

reply->add_properties_to_set()重新回传到init进程,然后在init的subcontext->Execute函数

中重新设置,当然这里的context_是vendor_init。

这里有2点值得思考

①为什么不在SubcontextProcess子进程中执行真正的设置属性的操作,而是要回传到init中去做该操作

我个人的理解是 propertyservice是运行在init父进程中,其最终设置只能在init父进程,子进程已经没有propertyservice的运行环境。

②上述操作可否不用传来传去,直接在init进程操作,过滤对应command传入到SubcontextProcess子进程?

这个我觉得应该可行

TransmitMessage里面的数据传输使用了Google Protocol Buffer格式,使用方法等可参见

https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html

https://www.cnblogs.com/dkblog/archive/2012/03/27/2419010.html

 

 

3. On section相关

上面章节讨论了service相关,该节我们来看一下on section相关,rc中,on section相关的解析函数为ActionParser,其解析主函数ParseSection实现如下

通过代码我们很明显的发现,subcontexts_和2章节中一致,这里的代码涉及到2部分,on event section 以及on property section,不管是event 还是property,最终都会生成action,只是on event section是event_trigger,而on properton是property_triggers。这里说明一下event_trigger,譬如on boot,那么event_trigger就是boot。

ParseTriggers函数就是用来解析event_trigger和property_triggers,从代码逻辑来看,可以发现一个section,可以是event_trigger或者property_triggers,或

event_trigger+property_triggers;一个event section只能有一个event_trigger+0个或者多个property_triggers,譬如(on)boot 或者(on) fs等;而property_triggers可以由多个property_trigger一起触发,譬如如下代码

on property:sys.usb.config=none && property:sys.usb.configfs=0

stop adbd

在ParsePropertyTrigger中,我们可以看到有如下逻辑

这里可以看到,如果subcontext不为空(也就是rc文件不在/system/),那么property_trigger是有合法性判断的,只有属性名字在kExportedActionableProperties或者属性是以kPartnerPrefixes开头的,才被允许进行property_trigger。

Section被解析正确后,会new一个action来保存

随后解析section的command,通过以下函数将其加入到action中

最终加入到action_managerde action队列中

 

我们来简单看一下action的执行流程

在init的main函数中

am.ExecuteOneCommand() -> action->ExecuteOneCommand(current_command_)

->action->ExecuteCommand

流程就走到了command.InvokeFunc(subcontext_),这里是不是很熟悉?对了,在上面service流程中已经讲述了整个InvokeFunc流程,这里就不累述了。

 

 

通过以上我们可以总结以下几点

①整个android系统action(command)执行入口是在init中(am.ExecuteOneCommand())。

②subcontext code的运行上下文是在init进程,SubcontextProcess 的运行上下文是在vendor_init子进程(vendor_init context)

③对于action(section command)而言,如果其定义是在vendor/odm分区,其会有subcontext关联,但是其command是否执行在vendor_init,需要看该command的execute_in_subcontext_是否为true,也就是builtin_functions中定义的第四个参数。

④对于service而言,我们可以看到do_start以及do_class_restart的execute_in_subcontext_都为false,所以service本身的执行域都是在init中,这里可以从service起来后的ppid可以看到,其都为1;但是onrestart的command话会根据③的规则选择运行的init,vendor_init进程

⑤可以看到setprop command {"setprop",{2,     2,    {true,   do_setprop}}},说明setprop命令如果subcontex存在,那么其会选择vendor_init作为运行域,但是通过Page7的分析我们可以得知,其最终还是会回传给init域去做处理,这里是我不太明白的地方,这里希望你们能够帮忙解答疑惑。

 

以上是鄙人对VENDOR_INIT相关内容进行的初步分析,其中内容可能有误,如果发现,请帮忙告知,不甚感激,谢谢。

 

 

<think>好的,我现在需要帮助用户解决关于Linux无线驱动中wiphy_vendor_command的实现或错误的问题。首先,我得先理解用户的问题到底是什么。用户提到了wiphy_vendor_command,这可能涉及到Linux内核中的无线子系统,特别是与厂商特定的命令相关的内容。 首先,我应该回忆一下Linux无线驱动的基本结构。Linux的无线子系统基于mac80211框架,而驱动开发者通常通过cfg80211和nl80211接口与内核交互。wiphy结构体代表一个无线设备,每个wiphy可以有多个虚拟接口(如sta、AP等)。vendor command允许硬件厂商实现特定的命令,这些命令不是标准化的,用于调试、私有功能等。 接下来,用户可能遇到的是如何正确实现这些vendor command,或者在实现过程中出现的错误。常见的错误可能包括命令无法注册、用户空间无法正确调用、内存问题或者权限问题等。我需要考虑如何指导用户进行正确的实现,或者如何排查这些错误。 实现vendor command通常涉及以下几个步骤:定义命令和属性,注册命令处理函数,处理用户空间的请求。在代码层面,开发者需要在驱动中定义struct wiphy_vendor_command结构,设置命令ID、策略、flags等,然后通过wiphy->vendor_commands数组注册这些命令。同时,需要实现对应的处理函数,处理来自用户空间的nl消息。 可能的错误点包括:命令ID冲突、策略定义不正确、未正确处理消息属性、内存分配问题、权限不足等。例如,如果策略定义不正确,内核在解析用户空间传来的属性时可能会失败,导致命令无法执行。此外,权限问题可能导致用户空间应用无法发送vendor command,需要检查NETLINK权限或CAP_NET_ADMIN能力。 对于错误解决方案,可能需要检查内核日志(dmesg)中的错误信息,查看是否有注册失败、参数解析失败等提示。另外,使用正确的用户空间工具发送命令也很重要,比如使用iw或自定义的nl80211程序,并确保命令格式正确,属性正确打包。 用户可能还需要了解如何调试vendor command的处理函数,例如使用printk输出调试信息,或者使用内核调试工具。此外,确保驱动编译进内核或正确加载,以及硬件固件版本兼容性也是需要注意的点。 现在,我需要整理这些思路,按照用户的要求,用中文给出结构清晰的回答,包括实现步骤、常见错误及解决方法,并生成相关问题。同时,引用提供的参考资料中的相关内容,虽然目前提供的引用看起来和无线驱动关系不大,但可能需要关联到GPLv2许可证相关的部分,比如在实现vendor command时需要遵守内核的许可规则,正确标记代码的许可证[^3]。 最后,生成的相关问题需要围绕vendor command的实现细节、调试方法、权限处理以及与其他内核模块的交互等方面展开。</think>### Linux无线驱动中wiphy_vendor_command的实现与错误解决 #### 一、wiphy_vendor_command基础概念 wiphy_vendor_command是Linux无线驱动中用于实现厂商自定义命令的接口,允许硬件厂商扩展非标准功能(如调试、私有协议等)。其核心数据结构为`struct wiphy_vendor_command`,通过`wiphy->vendor_commands`数组注册到内核[^3]。 #### 二、实现步骤 1. **定义命令与属性** ```c static const struct wiphy_vendor_command vendor_commands[] = { { .info = { .vendor_id = 0x1234, // 厂商ID .subcmd = 0x01, // 子命令ID }, .policy = VENDOR_CMD_ATTR_POLICY, // 属性策略 .maxattr = VENDOR_CMD_ATTR_MAX, .doit = vendor_cmd_handler, // 处理函数 }, }; ``` - **策略(policy)**:需定义`nla_policy`数组,用于验证用户空间传入的属性。 2. **注册到wiphy** ```c wiphy->vendor_commands = vendor_commands; wiphy->n_vendor_commands = ARRAY_SIZE(vendor_commands); ``` 3. **实现命令处理函数** ```c static int vendor_cmd_handler(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int data_len) { struct nlattr *tb[VENDOR_CMD_ATTR_MAX + 1]; nla_parse(tb, VENDOR_CMD_ATTR_MAX, data, data_len, NULL, NULL); // 解析并处理命令 return 0; } ``` #### 三、常见错误与解决方案 1. **命令注册失败** - **表现**:`dmesg`中输出`Failed to register vendor command`。 - **原因**:`vendor_id`冲突或内存分配失败。 - **解决**:检查厂商ID唯一性,确认内存分配正确。 2. **用户空间调用失败** - **表现**:`iw dev wlan0 vendor ...`返回`Operation not supported`。 - **原因**:驱动未正确注册命令,或用户空间权限不足。 - **解决**: - 检查驱动加载状态(`lsmod | grep <驱动名>`)。 - 确保用户进程具备`CAP_NET_ADMIN`权限。 3. **属性解析错误** - **表现**:内核日志提示`Attribute parse failed`。 - **原因**:`nla_policy`定义与用户空间传入属性不匹配。 - **解决**:重新核对策略定义,例如: ```c static struct nla_policy policy[VENDOR_CMD_ATTR_MAX + 1] = { [VENDOR_CMD_ATTR_DATA] = { .type = NLA_BINARY, .len = MAX_DATA_LEN }, }; ``` 4. **内存越界或泄漏** - **表现**:内核崩溃或`slab`错误。 - **解决**:在`vendor_cmd_handler`中使用`kmalloc`/`kfree`时严格校验长度,避免用户空间传递超长数据。 #### 四、调试技巧 1. **内核日志追踪** ```bash dmesg -wH | grep "vendor_cmd" ``` 2. **用户空间测试工具** ```bash iw dev wlan0 vendor recv 0x1234 0x01 0x00 # 发送命令 ``` 3. **GDB调试(需内核符号)** ```bash gdb /usr/lib/debug/boot/vmlinux-$(uname -r) (gdb) b vendor_cmd_handler ``` #### 五、许可证要求 实现vendor command需遵守GPLv2协议,所有代码文件需在头部明确标注许可证信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值