【连载】纯鸿蒙应用安全开发指南-公共事件安全开发

admin 2024年6月29日01:58:26评论2 views字数 21322阅读71分4秒阅读模式

这是本系列的第五篇文章,笔者带着对OpenHarmony组件公共事件开发的安全风险的一些浅见,与各位分享。随着HarmonyOS NEXT越来越近,希望能通过本系列文章为纯鸿蒙应用的开发者或者准开发者们带来一丝丝安全上的建议

一. 公共事件简介

Openharmony公共事件服务 CES(Common Event Service)提供了一对多的通信场景,为应用程序提供订阅、发布、退订公共事件的能力。公共事件从系统角度可分为:系统公共事件和自定义公共事件

  • • 系统公共事件:CES内部定义的公共事件,只有系统应用和系统服务才能发布,例如HAP安装,更新,卸载等公共事件。目前支持的系统公共事件详见系统公共事件定义

  • • 自定义公共事件:应用自定义一些公共事件用来实现跨进程的事件通信能力。

公共事件按发送方式可分为:无序公共事件有序公共事件粘性公共事件

  • • 无序公共事件:CES转发公共事件时,不考虑订阅者是否接收到,且订阅者接收到的顺序与其订阅顺序无关。

  • • 有序公共事件:CES转发公共事件时,根据订阅者设置的优先级等级,优先将公共事件发送给优先级较高的订阅者,等待其成功接收该公共事件之后再将事件发送给优先级较低的订阅者。如果有多个订阅者具有相同的优先级,则他们将随机接收到公共事件。

  • • 粘性公共事件:能够让订阅者收到在订阅前已经发送的公共事件就是粘性公共事件。普通的公共事件只能在订阅后发送才能收到,而粘性公共事件的特殊性就是可以先发送后订阅。发送粘性事件必须是系统应用或系统服务,且需要申请ohos.permission.COMMONEVENT_STICKY权限,配置方式请参阅访问控制授权申请指导

每个应用都可以按需订阅公共事件,订阅成功,当公共事件发布时,系统会将其发送给对应的应用。这些公共事件可能来自系统、其他应用和应用自身。

【连载】纯鸿蒙应用安全开发指南-公共事件安全开发

图1 公共事件示意图

二. 公共事件订阅/发布应用开发

公共事件发布

当需要发布某个自定义公共事件时,可以通过publish()方法发布事件。发布的公共事件可以携带数据,供订阅者解析并进行下一步处理。

  • • 发布公共事件接口

    接口名 接口描述
    publish(event: string, callback: AsyncCallback) 发布公共事件。
    publish(event: string, options: CommonEventPublishData, callback: AsyncCallback) 指定发布信息并发布公共事件。
    参数名 类型
    event string
    options CommonEventPublishData
    callback syncCallback
  • • CommonEventPublishData类型说明

    名称 读写属性 类型 描述
    bundleName 只读 string 表示包名称
    code 只读 int 表示公共事件的结果代码
    data 只读 string 表示公共事件的自定义结果数据
    subscriberPermissions 只读 Array 表示订阅者所需的权限
    isOrdered 只读 bool 表示是否是有序公共事件

示例如下

// 公共事件相关信息
var options ={
  code:2,// 公共事件的初始代码
  data:"client data",// 公共事件的初始数据
}

// 发布公共事件
commonEventManager.publish("event", options,(err) =>{
  if(err.code){
    Logger.error(TAG,`[CommonEvent]PublishCallBack err = ${JSON.stringify(err)}`)
  }else{
  Logger.info(TAG,"[CommonEvent]Publish2")
  }
})

若发布系统公共事件会在如下函数中进行校验,该文件中定义了全部的系统公共事件

// xref/base/notification/common_event_service/frameworks/native/src/common_event_support.cpp#2735
bool CommonEventSupport::IsSystemEvent(std::string &str)
{
    EVENT_LOGD("enter");
    std::vector<std::string>::iterator iter =find(commonEventSupport_.begin(), commonEventSupport_.end(), str);
    if(iter != commonEventSupport_.end()){
      returntrue;
    }
    returnfalse;
}

校验为系统事件后,Check 当前应用

  • • 是否为子系统应用,可以理解为框架层的 native 进程,通过 callerToken 校验是否为 Subsystem 应用;

  • • 通过 uid 获取应用系统是否为系统应用,通过应用授权配置文件 HarmonyAppProvision 中 app-feature 是否为 hos_system_app

  • • 是否为 CEM 调试工具发送的公共事件,校验 callerToken 是否为 shellToken,并且具备 PUBLISH_SYSTEM_COMMON_EVENT 权限,该权限为 SYSTEM_BASIC 权限

// xref/base/notification/common_event_service/services/src/inner_common_event_manager.cpp#378
bool InnerCommonEventManager::CheckUserId(const pid_t &pid, const uid_t &uid,
    const Security::AccessToken::AccessTokenID &callerToken, EventComeFrom &comeFrom, int32_t &userId)
{
    HITRACE_METER_NAME(HITRACE_TAG_NOTIFICATION, __PRETTY_FUNCTION__);
    EVENT_LOGD("enter");

    if(userId < UNDEFINED_USER){
        EVENT_LOGE("Invalid User ID %{public}d", userId);
        returnfalse;
    }
    comeFrom.isSubsystem =AccessTokenHelper::VerifyNativeToken(callerToken);
    if(!comeFrom.isSubsystem || supportCheckSaPermission_.compare("true")==0){
        comeFrom.isSystemApp =DelayedSingleton<BundleManagerHelper>::GetInstance()->CheckIsSystemAppByUid(uid);
    }
    comeFrom.isProxy = pid == UNDEFINED_PID;
    if((comeFrom.isSystemApp || comeFrom.isSubsystem || comeFrom.isCemShell)&&!comeFrom.isProxy){
        if(userId == CURRENT_USER){
            DelayedSingleton<OsAccountManagerHelper>::GetInstance()->GetOsAccountLocalIdFromUid(uid, userId);
        }elseif(userId == UNDEFINED_USER){
            userId = ALL_USER;
        }
    }else{
        if(userId == UNDEFINED_USER){
            DelayedSingleton<OsAccountManagerHelper>::GetInstance()->GetOsAccountLocalIdFromUid(uid, userId);
        }else{
            EVENT_LOGE("No permission to subscribe or send a common event to another user from uid = %{public}d", uid);
            returnfalse;
        }
    }
    returntrue;
}

// 校验是否为系统应用权限
if(isSystemEvent){
    EVENT_LOGD("System common event");
    if(!comeFrom.isSystemApp &&!comeFrom.isSubsystem &&!comeFrom.isCemShell){
        EVENT_LOGE(
        "No permission to send a system common event from %{public}s(pid = %{public}d, uid = %{public}d)"
        ", userId = %{public}d", bundleName.c_str(), pid, uid, userId);
        SendPublishHiSysEvent(user, bundleName, pid, uid, data.GetWant().GetAction(),false);
        returnfalse;
    }
}

公共事件订阅

动态订阅

动态订阅是指当应用在运行状态时对某个公共事件进行订阅,在运行期间如果有订阅的事件发布,那么订阅了这个事件的应用将会收到该事件及其传递的参数。例如,某应用希望在其运行期间收到电量过低的事件,并根据该事件降低其运行功耗,那么该应用便可动态订阅电量过低事件,收到该事件后关闭一些非必要的任务来降低功耗。订阅部分系统公共事件需要先申请权限,订阅这些事件所需要的权限请见公共事件权限列表

  • • 订阅公共事件接口

    接口名 接口描述
    createSubscriber(subscribeInfo: CommonEventSubscribeInfo, callback: AsyncCallback<CommonEventData>): void 创建订阅者对象(callback)
    createSubscriber(subscribeInfo: CommonEventSubscribeInfo): Promise 创建订阅者对象(promise)
    subscribe(subscriber: CommonEventSubscriber, callback: AsyncCallback): void 订阅公共事件
  • • 取消订阅

    接口名 接口描述
    unsubscribe(subscriber: CommonEventSubscriber, callback?: AsyncCallback) 取消订阅公共事件

动态订阅示例代码如下

// 订阅者信息
var subscribeInfo ={
    events:["event"],// 订阅event事件
}
// 创建订阅者
commonEventManager.createSubscriber(subscribeInfo,(err, subscriber) =>{
    if(err.code){
        Logger.error(TAG,`[commonEventManager]CreateSubscriberCallBack err = ${JSON.stringify(err)}`)
    }else{
        Logger.log(TAG,"[commonEventManager]CreateSubscriber")
        this.subscriber= subscriber
        this.result="Create subscriber succeed"
    }
})
if(this.subscriber!=null){
  commonEventManager.subscribe(this.subscriber,(err, data) =>{
    let callerUid = rpc.IPCSkeleton.getCallingUid();
    Logger.info(TAG,`[commonEventManager]SubscribeCallBack data= = ${callerUid}`);
    if(err.code){
        Logger.error(TAG,`[commonEventManager]SubscribeCallBack err= = ${JSON.stringify(err)}`)
    }else{
        Logger.info(TAG,`[commonEventManager]SubscribeCallBack data= = ${JSON.stringify(data)}`)
        this.result=`receive = ${callerUid}, event = ${data.event} , data = ${data.data}, code = ${data.code}`
    }
})

// 取消订阅者
// subscriber为订阅事件时创建的订阅者对象
if(subscriber !==null){
    commonEventManager.unsubscribe(subscriber,(err) =>{
        if(err){
            console.error(`[CommonEvent] UnsubscribeCallBack err=${JSON.stringify(err)}`);
        }else{
            console.info(`[CommonEvent] Unsubscribe`);
            subscriber =null
        }
    })
}

静态订阅

仅对系统应用开放,静态订阅者在未接收订阅的目标事件时,处于未拉起状态。当系统或应用发布了指定的公共事件后,静态订阅者将被拉起,并执行onReceiveEvent回调。开发者可以通过在onReceiveEvent回调中执行业务逻辑,实现当应用接收到特定公共事件时执行业务逻辑的目的。例如,应用希望在设备开机时执行一些初始化任务,那么该应用可以静态订阅开机事件,在收到开机事件后会拉起该应用,然后执行初始化任务。静态订阅是通过配置文件声明和实现继承自StaticSubscriberExtensionAbility的类实现对公共事件的订阅。需要注意的是,静态订阅公共事件对系统功耗有一定影响,建议谨慎使用。静态订阅需要在module.json5中配置订阅者信息

{
"module":{
    "extensionAbilities":[
        {
            "name":"StaticSubscriber",
            // 表示ExtensionAbility的入口文件路径
            "srcEntry":"./ets/staticsubscriber/StaticSubscriber.ts",
            "description":"$string:StaticSubscriber_desc",
            "icon":"$media:icon",
            "label":"$string:StaticSubscriber_label",
            // 静态订阅需要声明 staticSubscriber
            "type":"staticSubscriber",
            "exported":true,
            // ExtensionAbility的二级配置文件信息
            "metadata":[
            {
                // 对于静态订阅类型,name必须声明为ohos.extension.staticSubscriber
                "name":"ohos.extension.staticSubscriber",
                "resource":"$profile:subscribe"
            }]
        }
    ]
  }
}

// metadata 二级配置文件说明
{
    "commonEvents":[
    {
        // 静态订阅ExtensionAbility的名称,需要和module.json5中声明的ExtensionAbility的name一致。
       "name":"xxx",
       // 订阅者要求的发布者需要具备的权限,对于发布了目标事件但不具备permission中声明的权限的发布者将被视为非法事件不予处理。
       "permission":"xxx",
       // 订阅的目标事件列表。
       "events":[
           "xxx"
       ]
    }]
}

还需要修改设备的预配置文件/system/etc/app/install_list_capability.json,设备开机启动时会读取该配置文件,在应用安装会对在文件中配置的allowCommonEvent公共事件类型进行授权。预授权配置文件字段内容包括bundleNameapp_signatureallowCommonEvent(当前仅支持预置应用配置该文件)

  • • bundleName字段配置为应用的Bundle名称。

  • • app_signature字段配置为应用的指纹信息。指纹信息的配置请参见应用特权配置指南。

  • • allowCommonEvent字段配置为允许静态广播拉起的公共事件项。


【连载】纯鸿蒙应用安全开发指南-公共事件安全开发

三. 应用开发安全风险分析

公共事件作为进程间通信的一种机制,其安全边界就落在了进程间通信的接口上。对于订阅者而言,我们需要考虑可能存在的风险:

  • • 是否需要接收并处理任意应用的公共事件消息,是否可能导致订阅者的接口功能被滥用

  • • 公共事件的处理中是否存在一些逻辑上的问题,需要对外部事件数据做严格的校验

对于发送方而言:

  • • 是否有必要将公共事件发布给任意应用

  • • 公共事件发布数据中是否包含隐私信息或者敏感数据等

如上问题的解决方案大都可以使用权限控制来做风险消减,而对于外部数据的处理是否安全,这需要我们具体代码具体分析了。另外,对于不需要跨进程的事件通信,我们可以考虑使用 Emitter 来进行线程间通信。如下我们进一步详细介绍

跨进程接口访问控制

订阅者接口访问控制

对于静态订阅我们在上文的介绍中有说明,可以通过配置文件的方式添加事件发布方的权限要求。对于动态订阅公共事件,如果订阅的为自定义事件(系统公共事件普通应用无权限发送),且未声明发布者的权限要求,那么此订阅者将接收并处理任意三方应用的该订阅事件消息。举例来说,动态订阅的代码如下时

createSubscriber(){
    var subscribeInfo ={
    events:["example_event"],
}

commonEvent.createSubscriber(subscribeInfo,(err, subscriber) =>{
    if(err.code){
        Logger.error(TAG,`[CommonEvent]CreateSubscriberCallBack err = ${JSON.stringify(err)}`)
    }else{
        Logger.log(TAG,"[CommonEvent]CreateSubscriber")
        this.subscriber= subscriber
    }
})}

则任意三方应用均可向example_event的订阅者发送事件消息

publish(){
    commonEvent.publish("example_event",(err) =>{
        if(err.code){
            Logger.error(TAG,`[CommonEvent]PublishCallBack err = ${JSON.stringify(err)}`)
        }else{
            Logger.info(TAG,"[CommonEvent]Publish")
        }
    })
}

在某下业务场景下,我们不希望接收来自任意应用的事件消息,可以通过如下两种方式限制订阅者接口的暴露范围:

  • • 设置指定公共事件的发布方权限

  • • 设置指定公共事件的发布方包名( API11+ )

指定公共事件发布方需要具有的权限

关于静态订阅我们在上文中有介绍,在静态订阅的配置文件中可以指定公共事件发布方的权限信息。在动态订阅中,我们可以通过如下方式来限制只接收某些具有指定权限的发布方的消息,如下以ohos.permission.READ_MEDIA权限为例

var subscribeInfo = {
  events: ["example_event"],
  publisherPermission:"ohos.permission.READ_MEDIA",
}

配置权限后,尝试在发布者发布公共事件,可以看到日志中校验未通过,如下

C01200/Ces  foundation  W  [common_event_control_manager.cpp:(CheckSubscriberRequiredPermission):821] No permission to send common event example_event from com.example.mycepclient (pid = 14725, uid = 20010056), userId = 100 to ohos.samples.etscommonevent (pid = 24539, uid = 20010046), userId = 100 due to registered subscriber requires the ohos.permission.READ_MEDIA permission.

从报错的日志可以看到公共事件在被CES服务转发的过程中被拦截,OpenHarmony 中相关源码如下

bool CommonEventControlManager::CheckSubscriberRequiredPermission(const std::string &subscriberRequiredPermission,
    const CommonEventRecord &eventRecord, const EventSubscriberRecord &subscriberRecord)
{
    bool ret =false;
    if(subscriberRequiredPermission.empty()|| eventRecord.eventRecordInfo.isSubsystem){
        return true;
    }

    ret =AccessTokenHelper::VerifyAccessToken(eventRecord.eventRecordInfo.callerToken, subscriberRequiredPermission);
    if(!ret){
        EVENT_LOGW("No permission to send common event %{public}s "
        "from %{public}s (pid = %{public}d, uid = %{public}d), userId = %{public}d "
        "to %{public}s (pid = %{public}d, uid = %{public}d), userId = %{public}d "
        "due to registered subscriber requires the %{public}s permission.",
        eventRecord.commonEventData->GetWant().GetAction().c_str(),
        eventRecord.eventRecordInfo.bundleName.c_str(),
        eventRecord.eventRecordInfo.pid,
        eventRecord.eventRecordInfo.uid,
        eventRecord.userId,
        subscriberRecord.eventRecordInfo.bundleName.c_str(),
        subscriberRecord.eventRecordInfo.pid,
        subscriberRecord.eventRecordInfo.uid,
        subscriberRecord.eventSubscribeInfo->GetUserId(),
        subscriberRequiredPermission.c_str());
    }
return ret;
}

上述代码中,除了权限校验通过外,eventRecordSubsystem时也可以校验通过,是否为Subsystem的判定来自进程tokenid中的标志位type,当typeTOKEN_NATIVE时标记为subsystem应用。实际上我们在正常安装HAP应用时,tokenId 会被标记为TOKEN_HAP,那么什么情况下进程会被标记为TOKEN_NATI VE? 在本系列的上一篇文章中有介绍鸿蒙系统的启动流程,内核加载后启动/bin/init进程,init进程首先完成系统初始化工作,然后开始解析配置文件,配置文件分为三类:

  • • init.cfg默认配置文件,由 init 系统定义,优先解析。

  • • /system/etc/init/*.cfg 各子系统定义的配置文件。

  • • /vender/etc/init.cfg 厂商定义的配置文件。

在系统启动时会调用ParseAllServices读取etc/init目录下的 *.cfg 初始化系统服务配置项,并将全部服务进程加入全局链表g_initWorkspace.groupNode[NODE_TYPE_SERVICE]中,在启动accesstoken_service的 jobs 调用AddNewTokenToListAndFile为每个 service 分配 tokenid,其中使用/dev/urandom生成随机tokenid,并设置 token type 为 TOKEN_NATIVE_TYPE,如果进程名为hdcd则设置为TOKEN_SHELL_TYPE,然后将 token 等信息写入文件 /data/service/el0/access_token/nativetoken.json中,并在 jobs 中为该文件夹配置权限,普通进程无法访问

【连载】纯鸿蒙应用安全开发指南-公共事件安全开发

后续启动系统会直接调用AtlibInit尝试从/data/service/el0/access_token/nativetoken.json加载 token 信息。如上我们可以了解到,我们在开发公共事件订阅方时,针对一些场景可以合理配置权限来限制订阅接口的访问范围。

指定公共事件发布方的包名

通过如下方式指定公共事件发布者的包名为 com.samples.xxx ,注意publisherBundleName最低需要 API11 支持

var subscribeInfo ={
    events:["event"],
    publisherBundleName:"com.samples.xxx"
}

commonEvent.createSubscriber(subscribeInfo,(err, subscriber) =>{
    if(err.code){
        Logger.error(TAG,`CreateSubscriberCallBack err = ${JSON.stringify(err)}`)
    }else{
        Logger.log(TAG,"[CommonEvent]CreateSubscriber")
        this.subscriber= subscriber
        this.result="Create subscriber succeed"
    }
})

openharmony 源码校验位置如下,CES获取公共事件进行分发前,如下是动态订阅的代码

// xref/base/notification/common_event_service/services/src/common_event_subscriber_manager.cpp#377
void CommonEventSubscriberManager::GetSubscriberRecordsByWantLocked(const CommonEventRecord &eventRecord,
    std::vector<SubscriberRecordPtr> &records)
{
    std::lock_guard<std::mutex> lock(mutex_);
    if(eventSubscribers_.size()<=0){
        return;
    }

    auto recordsItem = eventSubscribers_.find(eventRecord.commonEventData->GetWant().GetAction());
    if(recordsItem == eventSubscribers_.end()){
        return;
    }

    bool isSystemApp =(eventRecord.eventRecordInfo.isSystemApp || eventRecord.eventRecordInfo.isSubsystem)&&
!eventRecord.eventRecordInfo.isProxy;

    auto bundleName = eventRecord.eventRecordInfo.bundleName;
    auto uid = eventRecord.eventRecordInfo.uid;

    for(auto it =(recordsItem->second).begin(); it !=(recordsItem->second).end(); it++){
        if((*it)->eventSubscribeInfo ==nullptr){
            continue;
        }

        if(!(*it)->eventSubscribeInfo->GetMatchingSkills().Match(eventRecord.commonEventData->GetWant())){
            continue;
        }

        // publisher指定接收方bundlename
        if(!eventRecord.publishInfo->GetBundleName().empty()&&
            eventRecord.publishInfo->GetBundleName()!=(*it)->eventRecordInfo.bundleName){
            continue;
        }

        // subscriber指定发送方bundlename
        auto publisherBundleName =(*it)->eventSubscribeInfo->GetPublisherBundleName();
        if(!publisherBundleName.empty()&& publisherBundleName != bundleName){
            continue;
        }

        auto publisherUid =(*it)->eventSubscribeInfo->GetPublisherUid();
        if(publisherUid >0&& uid >0&&static_cast<uid_t>(publisherUid)!= uid){
            continue;
        }

        if(CheckSubscriberByUserId((*it)->eventSubscribeInfo->GetUserId(), isSystemApp, eventRecord.userId)){
            records.emplace_back(*it);
        }
    }
}

那么问题来了,发布者是否可以控制包名信息?答案是否定的。应用程序和CES的通信走 binder 的 IPC 通信,在CES侧调用如下代码获取客户端的Uid

// xref/base/notification/common_event_service/services/src/common_event_manager_service.cpp#121
int32_t CommonEventManagerService::PublishCommonEvent(const CommonEventData &event,
    const CommonEventPublishInfo &publishinfo, const sptr<IRemoteObject> &commonEventListener,
    const int32_t &userId)
{
    EVENT_LOGD("enter");

    if(!IsReady()){
        EVENT_LOGE("CommonEventManagerService not ready");
        return ERR_NOTIFICATION_CESM_ERROR;
    }

    if(userId != ALL_USER && userId != CURRENT_USER && userId != UNDEFINED_USER){
        bool isSubsystem =AccessTokenHelper::VerifyNativeToken(IPCSkeleton::GetCallingTokenID());
    if(!isSubsystem &&!AccessTokenHelper::IsSystemApp()){
        EVENT_LOGE("publish to special user must be system application.");
        return ERR_NOTIFICATION_CES_COMMON_NOT_SYSTEM_APP;
    }
}

return PublishCommonEventDetailed(event,
        publishinfo,
        commonEventListener,
IPCSkeleton::GetCallingPid(),
IPCSkeleton::GetCallingUid(),
IPCSkeleton::GetCallingTokenID(),
        userId);
}

跟入 PublishCommonEventDetailed ,其中使用 BundleManagerHelper 的 GetBundleName 获取包名

// xref/base/notification/common_event_service/services/src/common_event_manager_service.cpp#PublishCommonEventDetailed
int32_t CommonEventManagerService::PublishCommonEventDetailed(const CommonEventData&event,
constCommonEventPublishInfo&publishinfo,const sptr<IRemoteObject>&commonEventListener,constpid_t&pid,
constuid_t&uid,constint32_t&clientToken,constint32_t&userId)
{
// ...
    std::string bundleName =DelayedSingleton<BundleManagerHelper>::GetInstance()->GetBundleName(uid);
    bool ret = innerCommonEventManager->PublishCommonEvent(event,
        publishinfo,
        commonEventListener,
        recordTime,
        pid,
        uid,
        clientToken,
        userId,
        bundleName,
        commonEventManagerService);
}

关于 GetBundleName ,与本系列中的文章 ServiceExternsionAbility 中介绍的 getBundleNameByUid 底层是一致的,调用 GetNameForUid 获取的 BundleName 在应用安装时与 uid 建立映射关系,在 uid 可信的情况下,获取的 BundleName 也是可信的。

发布者事件访问控制

指定公共事件订阅者的包名

同样在某些场景下,我们发布方的公共事件中可能会包含某些敏感信息,为了保证敏感信息不被任意第三方应用接收,我们可以指定接收方的包名以及接收方所需具有的权限。通过上文中公共事件发布的相关 API 介绍说明,我们可以通过如下方式指定接收方的包名为 com.samples.xxx,所需权限信息为 ohos.permission.READ_MEDIA,需要注意的是bundleNamesubscriberPermissions同样需要 API Version 11 的支持

var options ={
    code:1,// Initial code for CommonEvent
    data:"initial data",// Initial data for CommonEvent
    //data: { info:"hello" }, // Type '{ info: string; }' is not assignable to type 'string'. <tsCheck>
    bundleName:"com.samples.xxx",
    subscriberPermissions:"ohos.permission.READ_MEDIA"
}
// Publish CommonEvent
commonEvent.publish("event", options,(err) =>{
    if(err.code){
        Logger.error(TAG,`[CommonEvent]PublishCallBack err = ${JSON.stringify(err)}`)
    }else{
        Logger.info(TAG,"[CommonEvent]Publish2")
    }
})

Openharmony 源码中校验包名位置,如下46行,通过比较 publishInfo 中的 bundleName 与 subscriber 的 bundleName,如下是静态订阅者的校验,动态订阅见上节中分析

// xref/base/notification/common_event_service/services/src/static_subscriber_manager.cpp#153
void StaticSubscriberManager::PublishCommonEventInner(const CommonEventData&data,
constCommonEventPublishInfo&publishInfo,constSecurity::AccessToken::AccessTokenID&callerToken,
constint32_t&userId,const sptr<IRemoteObject>&service,const std::string &bundleName)
{
    auto targetSubscribers = validSubscribers_.find(data.GetWant().GetAction());
    if(targetSubscribers != validSubscribers_.end()){
        for(auto subscriber : targetSubscribers->second){
            EVENT_LOGI("subscriber.userId = %{public}d, userId = %{public}d, event = %{public}s", subscriber.userId,
                userId, data.GetWant().GetAction().c_str());
            if(IsDisableEvent(subscriber.bundleName, targetSubscribers->first)){
                EVENT_LOGD("Current subscriber is disable, subscriber.userId = %{public}d.", subscriber.userId);
                SendStaticEventProcErrHiSysEvent(
                    userId, bundleName, subscriber.bundleName, data.GetWant().GetAction());
                continue;
            }
            if(subscriber.userId < SUBSCRIBE_USER_SYSTEM_BEGIN){
                EVENT_LOGW("subscriber userId is invalid, subscriber.userId = %{public}d", subscriber.userId);
                SendStaticEventProcErrHiSysEvent(userId, bundleName, subscriber.bundleName, data.GetWant().GetAction());
                continue;
            }
            if((subscriber.userId > SUBSCRIBE_USER_SYSTEM_END)&&(userId != ALL_USER)
&&(subscriber.userId != userId)){
                EVENT_LOGW("subscriber userId is not match, subscriber.userId = %{public}d, userId = %{public}d",
                    subscriber.userId, userId);
                SendStaticEventProcErrHiSysEvent(userId, bundleName, subscriber.bundleName, data.GetWant().GetAction());
                continue;
            }
            // judge if app is system app.
            if(!DelayedSingleton<BundleManagerHelper>::GetInstance()->
                CheckIsSystemAppByBundleName(subscriber.bundleName, subscriber.userId)){
                EVENT_LOGW("subscriber is not system app, not allow.");
                continue;
            }
            if(!VerifyPublisherPermission(callerToken, subscriber.permission)){
                EVENT_LOGW("publisher does not have required permission %{public}s", subscriber.permission.c_str());
                SendStaticEventProcErrHiSysEvent(userId, bundleName, subscriber.bundleName, data.GetWant().GetAction());
                continue;
            }
            if(!VerifySubscriberPermission(subscriber.bundleName, subscriber.userId,
                publishInfo.GetSubscriberPermissions())){
                EVENT_LOGW("subscriber does not have required permissions");
                SendStaticEventProcErrHiSysEvent(userId, bundleName, subscriber.bundleName, data.GetWant().GetAction());
                continue;
            }
            if(!publishInfo.GetBundleName().empty()&& subscriber.bundleName != publishInfo.GetBundleName()){
                EVENT_LOGW("subscriber bundleName is not match, subscriber.bundleName = %{public}s, "
                "bundleName = %{public}s", subscriber.bundleName.c_str(), publishInfo.GetBundleName().c_str());
                continue;
            }
            PublishCommonEventConnecAbility(data, service, subscriber.userId, subscriber.bundleName, subscriber.name);
        }
    }
}

进程内订阅发布事件

在某些场景下,我们希望使用事件订阅发布机制,但又不需要事件跨进程通信,为了有效减少上一节中存在的接口访问控制的风险面,这种情况下我们可以考虑使用 Emitter 进行线程间通信。Emitter 的开发步骤示例如下:

订阅事件

订阅事件开发示例

import emitter from "@ohos.events.emitter";

// 定义一个eventId为1的事件
let event ={
    eventId:1
};

// 收到eventId为1的事件后执行该回调
let callback =(eventData)=>{
    console.info('event callback');
};

// 订阅eventId为1的事件
emitter.on(event, callback);

发送事件

发送事件开发示例

import emitter from "@ohos.events.emitter";

// 定义一个eventId为1的事件,事件优先级为Low
let event ={
    eventId:1,
    priority: emitter.EventPriority.LOW
};

let eventData ={
    data:{
        "content":"c",
        "id":1,
        "isEmpty":false,
    }
};

// 发送eventId为1的事件,事件内容为eventData
emitter.emit(event, eventData);

在事件通知不需要跨进程的场景中,使用 Emitter 无疑是一个不错的选择 ~

参考

  • • https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/common-event-overview-0000001427744568-V2

  • • https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/itc-with-emitter-0000001427584616-V2

原文始发于微信公众号(银针安全):【连载】纯鸿蒙应用安全开发指南-公共事件安全开发

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月29日01:58:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【连载】纯鸿蒙应用安全开发指南-公共事件安全开发https://cn-sec.com/archives/2898006.html

发表评论

匿名网友 填写信息