鸿蒙OpenHarmony应用程序安全模型

2022年2月19日

注:通过代码走读方式了解流程和概述,部分细节有待完善

代码版本:基于OpenHarmony在线代码https://gitee.com/organizations/openharmony/projects

先抛出问题和疑问:

1,OpenHarmony应用程序安全底座是什么?应用整体安全性有哪些方面,是如何保证的?

2,OpenHarmony内核层是基于Linux,应用程序安全模型应该与Android系统的应用程序基础安全模型差异不大?但是目前版本可能存在不完善的地方和差异,从kernel->frameworks->app各个层面具体差异在哪?设计思想差异等

内核子系统:采用多内核(Linux内核或者LiteOS)设计,支持针对不同资源受限设备选用适合的OS内核。内核抽象层(KAL,Kernel Abstract Layer)通过屏蔽多内核差异,对上层提供基础的内核能力,包括进程/线程管理、内存管理、文件系统、网络管理和外设管理等。

3,OpenHarmony应用程序启动流程是如何的?

4,OpenHarmony应用程序沙盒机制的安全性设计是如何实现的?与Android系统差异性如何?

以下问题待研究学习,不在此文章

5,OpenHarmony应用程序是否有组件安全?数据安全等如何保证?

6,OpenHarmony应用程序运行时载入机制具体流程?

7,OpenHarmony应用程序的IPC机制如何?如何保证安全性?

8,宏内核与微内核在安全性方面区别?微内核裁剪了哪些安全机制

8,JS技术栈在目前鸿蒙系统中是否新引入风险?是如何保证的?

9,分布式跨端拉起FA除了设备认证外是如何保证安全性的?是否涉及到分布式权限管理?

OpenHarmony代码库与应用程序启动有关库(搜索startup关键字)

以下主要分析starup_appspawn应用孵化模块这个库相关代码

FireShot Capture 001 - OpenHarmony - Open_Harmony - Gitee.com - gitee.com.png

appspawn应用孵化器组件

查看官方介绍

FireShot Capture 002 - startup_appspawn_ Appspawn module for spawning application processes _ - gitee.com.png

所以AppSpwan类似于Android中的Zygote,负责对OpenHarmony中的应用程序启动等工作,同样也是采用本地Socket方式进行通讯。

以下是核心代码:
代码路径位于:https://gitee.com/openharmony/startup_appspawn/blob/master/src/appspawn_server.cpp

ServerMain函数中创建进程:


    while (isRunning_) {
        std::unique_lock<std::mutex> lock(mut_);
        dataCond_.wait(lock, [this] { return !this->appQueue_.empty(); });
        std::unique_ptr<AppSpawnMsgPeer> msg = std::move(appQueue_.front());
        appQueue_.pop();
        int connectFd = msg->GetConnectFd();
        ClientSocket::AppProperty *appProperty = msg->GetMsg();
        if (!CheckAppProperty(appProperty)) {
            msg->Response(-EINVAL);
            continue;
        }

        int32_t fd[FDLEN2] = {FD_INIT_VALUE, FD_INIT_VALUE};
        int32_t buff = 0;
        if (pipe(fd) == -1) {
            HiLog::Error(LABEL, "create pipe fail, errno = %{public}d", errno);
            msg->Response(ERR_PIPE_FAIL);
            continue;
        }

        InstallSigHandler();
        pid_t pid = fork();
        if (pid < 0) {
            HiLog::Error(LABEL, "AppSpawnServer::Failed to fork new process, errno = %{public}d", errno);
            close(fd[0]);
            close(fd[1]);
            msg->Response(-errno);
            continue;
        } else if (pid == 0) {
            SpecialHandle(appProperty);
            return SetAppProcProperty(connectFd, appProperty, longProcName, longProcNameLen, fd);
        }

        read(fd[0], &buff, sizeof(buff));  // wait child process resutl
        close(fd[0]);
        close(fd[1]);

        HiLog::Info(LABEL, "child process init %{public}s", (buff == ERR_OK) ? "success" : "fail");
        (buff == ERR_OK) ? msg->Response(pid) : msg->Response(buff);  // response to AppManagerService
        socket_->CloseConnection(connectFd);                          // close socket connection
        HiLog::Debug(LABEL, "AppSpawnServer::parent process create app finish, pid = %{public}d", pid);
    }
    return false;
}

设置UID/GID


    // set gid
    if (setresgid(gid, gid, gid) == -1) {
        HiLog::Error(LABEL, "setgid(%{public}u) failed: %{public}d", gid, errno);
        return (-errno);
    }

    // If the effective user ID is changed from 0 to nonzero, then all capabilities are cleared from the effective set
    if (setresuid(uid, uid, uid) == -1) {
        HiLog::Error(LABEL, "setuid(%{public}u) failed: %{public}d", uid, errno);
        return (-errno);
    }
    return ERR_OK;
}

还有设置权能,进程名称,应用沙盒数据目录等,此部分与Android代码非常类似,同时可以看到OpenHarmony的应用程序安全基础模型也是基于Linux UID/GID/权能等,即基于Linux安全模型,每个应用都是相互隔离的。

OpenHarmony的应用程序数据沙盒目录:
/mnt/sandbox/<packagename>
/mnt/sandbox/<packagename>/data/storage
/data/accounts/account_0/applications/
/data/accounts/account_0/appdata/
/data/storage/el1

同时可以看到通过WITH_SELINUX宏控制设置进程SELINUX属性:


    HapContext hapContext;
    ret = hapContext.HapDomainSetcontext(appProperty->apl, appProperty->processName);
    if (ret != 0) {
        HiLog::Error(LABEL, "AppSpawnServer::Failed to hap domain set context, errno = %{public}d", errno);
    }

走读一下OpenHarmony应用启动流程(详细描述用时序图,此处省略)

应用启动流程主要流程在于 用户程序框架子系统,代码路径https://gitee.com/openharmony/appexecfwk_standard

通过用户程序框架子系统appexecfwk的框架图看一下AppSpawn模块位置:

appexecfwk.png

1, 从Launcher系统桌面点击图标开始启动应用
startAbility类似Android中的startActivity
代码路径位于Launcer工程中的:
./common/src/main/ets/default/manager/LauncherAbilityManager.ets

import featureAbility from '@ohos.ability.featureAbility';

 /**
   * 启动应用
   *
   * @params paramAbilityName Ability名
   * @params paramBundleName 应用包名
   */
  public startLauncherAbility(paramAbilityName, paramBundleName) {
    console.info('Launcher ==> LauncherAbilityManager startApplication abilityName => ' + paramBundleName + "bundleName");
    let result = featureAbility.startAbility({
      want: {
        bundleName: paramBundleName,
        abilityName: paramAbilityName
      }
    }).then((data) => {
      console.info("Launcher ==> LauncherAbilityManager startApplication promise::then : "  + JSON.stringify(data));
    }).catch((error) => {
      console.info("Launcher ==> LauncherAbilityManager startApplication promise::catch : "  + JSON.stringify(error));
    });
    console.info("Launcher ==> LauncherAbilityManager startApplication AceApplication : startAbility : "  + result);
  }
}

2,通过NAPI接口调用到用户框架子系统服务真正操作函数逻辑
(NAPI类似于JNI,中间还经过js framework,js engine)

OpenHarmony-v3.0-LTS/foundation/aafwk/standard/interfaces/kits/napi/aafwk/featureAbility/feature_ability.cpp

3,Ability元能力框架子系统中流程
(类似Android中的AMS)
https://gitee.com/openharmony/docs/blob/master/zh-cn/readme/%E5%85%83%E8%83%BD%E5%8A%9B%E5%AD%90%E7%B3%BB%E7%BB%9F.md

[https://gitee.com/openharmony/aafwk_standard/blob/master/services/abilitymgr/src/ability_manager_client.cpp](https://gitee.com/openharmony/aafwk_standard/blob/master/services/abilitymgr/src/ability_manager_client.cpp)

ErrCode AbilityManagerClient::StartAbility(const Want &want, int requestCode, int32_t userId)
{
    CHECK_REMOTE_OBJECT_AND_RETURN(remoteObject_, ABILITY_SERVICE_NOT_CONNECTED);
    sptr<IAbilityManager> abms = iface_cast<IAbilityManager>(remoteObject_);
    return abms->StartAbility(want, userId, requestCode);
}

ErrCode AbilityManagerClient::StartAbility(
    const Want &want, const sptr<IRemoteObject> &callerToken, int requestCode, int32_t userId)
{
    CHECK_REMOTE_OBJECT_AND_RETURN(remoteObject_, ABILITY_SERVICE_NOT_CONNECTED);
    HILOG_INFO("%{public}s called, bundleName=%{public}s, abilityName=%{public}s, userId=%{public}d",
        __func__, want.GetElement().GetBundleName().c_str(), want.GetElement().GetAbilityName().c_str(), userId);
    sptr<IAbilityManager> abms = iface_cast<IAbilityManager>(remoteObject_);
    return abms->StartAbility(want, callerToken, userId, requestCode);
}

ErrCode AbilityManagerClient::StartAbility(const Want &want, const AbilityStartSetting &abilityStartSetting,
    const sptr<IRemoteObject> &callerToken, int requestCode, int32_t userId)
{
    CHECK_REMOTE_OBJECT_AND_RETURN(remoteObject_, ABILITY_SERVICE_NOT_CONNECTED);
    sptr<IAbilityManager> abms = iface_cast<IAbilityManager>(remoteObject_);
    return abms->StartAbility(want, abilityStartSetting, callerToken, userId, requestCode);
}
[https://gitee.com/openharmony/aafwk_standard/blob/master/services/abilitymgr/src/ability_manager_service.cpp](https://gitee.com/openharmony/aafwk_standard/blob/master/services/abilitymgr/src/ability_manager_service.cpp)

int AbilityManagerService::StartAbility(const Want &want, const sptr<IRemoteObject> &callerToken,
    int32_t userId, int requestCode)
{
    BYTRACE_NAME(BYTRACE_TAG_ABILITY_MANAGER, __PRETTY_FUNCTION__);
    auto flags = want.GetFlags();
    if ((flags & Want::FLAG_ABILITY_CONTINUATION) == Want::FLAG_ABILITY_CONTINUATION) {
        HILOG_ERROR("StartAbility with continuation flags is not allowed!");
        return ERR_INVALID_VALUE;
    }
    HILOG_INFO("%{public}s", __func__);
    if (CheckIfOperateRemote(want)) {
        HILOG_INFO("AbilityManagerService::StartAbility. try to StartRemoteAbility");
        return StartRemoteAbility(want, requestCode);
    }
    HILOG_INFO("AbilityManagerService::StartAbility. try to StartLocalAbility");
    return StartAbilityInner(want, callerToken, requestCode, -1, userId);
}

int AbilityManagerService::StartAbilityInner(const Want &want, const sptr<IRemoteObject> &callerToken,
    int requestCode, int callerUid, int32_t userId)
{
    BYTRACE_NAME(BYTRACE_TAG_ABILITY_MANAGER, __PRETTY_FUNCTION__);
    HILOG_DEBUG("%{public}s begin.", __func__);
    if (callerToken != nullptr && !VerificationAllToken(callerToken)) {
        HILOG_ERROR("%{public}s VerificationAllToken failed.", __func__);
        return ERR_INVALID_VALUE;
    }

LoadAbility的时候发现如何应用进程没有起来则通过socket和AppSpwan连接,发送参数给孵化器创建应用进程。

void AppMgrServiceInner::LoadAbility(const sptr<IRemoteObject> &token, const sptr<IRemoteObject> &preToken,
    const std::shared_ptr<AbilityInfo> &abilityInfo, const std::shared_ptr<ApplicationInfo> &appInfo,
    const std::shared_ptr<AAFwk::Want> &want)
{
    BYTRACE_NAME(BYTRACE_TAG_APP, __PRETTY_FUNCTION__);
    if (!CheckLoadabilityConditions(token, abilityInfo, appInfo)) {
        APP_LOGE("CheckLoadabilityConditions failed");
        return;
    }

    if (!appRunningManager_) {
        APP_LOGE("appRunningManager_ is nullptr");
        return;
    }

    BundleInfo bundleInfo;
    HapModuleInfo hapModuleInfo;
    if (!GetBundleAndHapInfo(*abilityInfo, appInfo, bundleInfo, hapModuleInfo)) {
        APP_LOGE("GetBundleAndHapInfo failed");
        return;
    }

    std::string processName;
    MakeProcessName(processName, abilityInfo, appInfo);
    APP_LOGI("processName = [%{public}s]", processName.c_str());

    auto appRecord =
        appRunningManager_->CheckAppRunningRecordIsExist(appInfo->name, processName, appInfo->uid, bundleInfo);
    if (!appRecord) {
        appRecord =
            CreateAppRunningRecord(token, preToken, appInfo, abilityInfo, processName, bundleInfo, hapModuleInfo);
        if (!appRecord) {
            APP_LOGI("CreateAppRunningRecord failed, appRecord is nullptr");
            return;
        }
        StartProcess(abilityInfo->applicationName, processName, appRecord,
            abilityInfo->applicationInfo.uid, abilityInfo->applicationInfo.bundleName);
    } else {
        StartAbility(token, preToken, abilityInfo, appRecord, hapModuleInfo);
    }
    PerfProfile::GetInstance().SetAbilityLoadEndTime(GetTickCount());
    PerfProfile::GetInstance().Dump();
    PerfProfile::GetInstance().Reset();
}
void AppMgrServiceInner::StartProcess(const std::string &appName, const std::string &processName,
    const std::shared_ptr<AppRunningRecord> &appRecord, const int uid, const std::string &bundleName)
{
    BYTRACE_NAME(BYTRACE_TAG_APP, __PRETTY_FUNCTION__);
    if (!remoteClientManager_->GetSpawnClient() || !appRecord) {
        APP_LOGE("appSpawnClient or apprecord is null");
        return;
    }

    auto bundleMgr_ = remoteClientManager_->GetBundleManager();
    if (bundleMgr_ == nullptr) {
        APP_LOGE("GetBundleManager fail");
        return;
    }

    auto userId = GetUserIdByUid(uid);
    AppSpawnStartMsg startMsg;
    BundleInfo bundleInfo;
    std::vector<AppExecFwk::BundleInfo> bundleInfos;
    bool bundleMgrResult = bundleMgr_->GetBundleInfos(AppExecFwk::BundleFlag::GET_BUNDLE_WITH_ABILITIES,
        bundleInfos, userId);
    if (!bundleMgrResult) {
        APP_LOGE("GetBundleInfo is fail");
        return;
    }

    auto isExist = [&bundleName, &uid](const AppExecFwk::BundleInfo &bundleInfo) {
        return bundleInfo.name == bundleName && bundleInfo.uid == uid;
    };
    auto bundleInfoIter = std::find_if(bundleInfos.begin(), bundleInfos.end(), isExist);
    if (bundleInfoIter == bundleInfos.end()) {
        APP_LOGE("Get target fail.");
        return;
    }
    startMsg.uid = (*bundleInfoIter).uid;
    startMsg.gid = (*bundleInfoIter).gid;
    startMsg.accessTokenId = (*bundleInfoIter).applicationInfo.accessTokenId;
    startMsg.apl = (*bundleInfoIter).applicationInfo.appPrivilegeLevel;
    startMsg.bundleName = bundleName;
    APP_LOGD("StartProcess come, accessTokenId: %{public}d, apl: %{public}s, bundleName: %{public}s",
        startMsg.accessTokenId, startMsg.apl.c_str(), bundleName.c_str());

    bundleMgrResult = bundleMgr_->GetBundleGidsByUid(bundleName, uid, startMsg.gids);
    if (!bundleMgrResult) {
        APP_LOGE("GetBundleGids is fail");
        return;
    }
    startMsg.procName = processName;
    startMsg.soPath = SO_PATH;

    PerfProfile::GetInstance().SetAppForkStartTime(GetTickCount());
    pid_t pid = 0;
    ErrCode errCode = remoteClientManager_->GetSpawnClient()->StartProcess(startMsg, pid);
    if (FAILED(errCode)) {
        APP_LOGE("failed to spawn new app process, errCode %{public}08x", errCode);
        appRunningManager_->RemoveAppRunningRecordById(appRecord->GetRecordId());
        return;
    }
    APP_LOGI("newPid:%{public}d uid:%{public}d", pid, startMsg.uid);
    appRecord->GetPriorityObject()->SetPid(pid);
    appRecord->SetUid(startMsg.uid);
    OptimizerAppStateChanged(appRecord, ApplicationState::APP_STATE_CREATE);
    appRecord->SetAppMgrServiceInner(weak_from_this());
    OnAppStateChanged(appRecord, ApplicationState::APP_STATE_CREATE);
    AddAppToRecentList(appName, appRecord->GetProcessName(), pid, appRecord->GetRecordId());
    OnProcessCreated(appRecord);
    PerfProfile::GetInstance().SetAppForkEndTime(GetTickCount());
}

总结:

1,OpenHarmony应用程序安全模型基于Linux UID/GID基础安全模型
2,从OpenHarmony应用程序沙盒逻辑中看到有mount namespace数据隔离等,是否支持动态存储权限待继续研究
3,OpenHarmony应用程序安全模型缺少底层应用进程资源控制SetRLimits,原因未明,可能未考虑手机等富设备支持,并且基于LiteOs内核设计
4,OpenHarmony在线代码中看到SELINUX已并入主线(具体再开章节研究),并且支持应用进程域

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容