鴻蒙OpenHarmony 安全子系統(tǒng)-ATM(AccessToken)

庫代碼地址:https://gitee.com/openharmony/security_access_token

以下引用官方介紹:


FireShot Capture 715 - security_access_token_ ATM(AccessTokenManager)是OpenHarmony上基于AccessTo_ - gitee.com.png

以下是代碼及主要流程分析:

創(chuàng)建AccessTokenId
通過隨機數(shù)創(chuàng)建后保存在
#define TOKEN_ID_CFG_FILE_PATH "/data/service/el0/access_token/nativetoken.json"
https://gitee.com/openharmony/security_access_token/blob/master/interfaces/innerkits/nativetoken/src/nativetoken.c

NativeAtId CreateNativeTokenId(void)
{
    uint32_t rand;
    NativeAtId tokenId;
    AtInnerInfo *innerId = (AtInnerInfo *)(&tokenId);

    int ret = GetRandomTokenId(&rand);
    if (ret != ATRET_SUCCESS) {
        return 0;
    }

    innerId->reserved = 0;
    innerId->tokenUniqueId = rand & (0xFFFFFF);
    innerId->type = TOKEN_NATIVE_TYPE;
    innerId->version = 1;
    return tokenId;
}

使用者:

native進程
在native進程拉起前,需要調(diào)用GetAccessTokenId函數(shù),獲取該native進程的TokenID;再調(diào)用SetSelfTokenID將進程TokenID設(shè)置到內(nèi)核中。
在native進程運行過程中,可以通過調(diào)用GetNativeTokenInfo、CheckNativeDCap來查驗對應(yīng)進程所具備的token信息,包括分布式能力、APL等級等信息。

https://gitee.com/openharmony/startup_init_lite/blob/master/services/init/standard/init_service.c

int SetAccessToken(const Service *service)
{
    INIT_ERROR_CHECK(service != NULL, return SERVICE_FAILURE, "%s failed", service->name);
    int ret = SetSelfTokenID(service->tokenId);
    INIT_LOGI("%s: token id %lld, set token id result %d", service->name, service->tokenId, ret);
    return ret == 0 ? SERVICE_SUCCESS : SERVICE_FAILURE;
}

void GetAccessToken(void)
{
    InitGroupNode *node = GetNextGroupNode(NODE_TYPE_SERVICES, NULL);
    while (node != NULL) {
        Service *service = node->data.service;
        if (service != NULL) {
            if (service->capsArgs.count == 0) {
                service->capsArgs.argv = NULL;
            }
            if (strlen(service->apl) == 0) {
                (void)strncpy_s(service->apl, sizeof(service->apl), "system_core", sizeof(service->apl) - 1);
            }
            uint64_t tokenId = GetAccessTokenId(service->name, (const char **)service->capsArgs.argv,
                service->capsArgs.count, service->apl);
            if (tokenId  == 0) {
                INIT_LOGE("Get totken id %lld of service \' %s \' failed", tokenId, service->name);
            }
            service->tokenId = tokenId;
        }
        node = GetNextGroupNode(NODE_TYPE_SERVICES, node);
    }
}

系統(tǒng)調(diào)用入口并寫入內(nèi)核
https://gitee.com/openharmony/security_access_token/blob/master/interfaces/innerkits/token_setproc/src/token_setproc.c

設(shè)備驅(qū)動
#define TOKENID_DEVNODE "/dev/access_token_id"

set接口:

int SetSelfTokenID(uint64_t tokenID)
{
    int fd = open(TOKENID_DEVNODE, O_RDWR);
    if (fd < 0) {
        return ACCESS_TOKEN_ERROR;
    }
    int ret = ioctl(fd, ACCESS_TOKENID_SET_TOKENID, &tokenID);
    if (ret) {
        close(fd);
        return ACCESS_TOKEN_ERROR;
    }

    close(fd);
    return ACCESS_TOKEN_OK;
}

get接口:

uint64_t GetSelfTokenID()
{
    uint64_t token = INVAL_TOKEN_ID;
    int fd = open(TOKENID_DEVNODE, O_RDWR);
    if (fd < 0) {
        return INVAL_TOKEN_ID;
    }
    int ret = ioctl(fd, ACCESS_TOKENID_GET_TOKENID, &token);
    if (ret) {
        close(fd);
        return INVAL_TOKEN_ID;
    }

    close(fd);
    return token;
}
內(nèi)核驅(qū)動

../kernel/linux/linux-5.10/drivers/accesstokenid/access_tokenid.c

dev設(shè)備驅(qū)動入內(nèi)核層調(diào)用入口

static long access_tokenid_ioctl(struct file *file, unsigned int cmd,
                 unsigned long arg)
{
    void __user *uarg = (void __user *)arg;
    unsigned int func_cmd = _IOC_NR(cmd);

    if (uarg == NULL) {
        pr_err("%s: invalid user uarg\n", __func__);
        return -EINVAL;
    }

    if (_IOC_TYPE(cmd) != ACCESS_TOKEN_ID_IOCTL_BASE) {
        pr_err("%s: access tokenid magic fail, TYPE=%d\n",
               __func__, _IOC_TYPE(cmd));
        return -EINVAL;
    }

    if (func_cmd >= ACCESS_TOKENID_MAX_NR) {
        pr_err("%s: access tokenid cmd error, cmd:%d\n",
            __func__, func_cmd);
        return -EINVAL;
    }

    if (g_func_array[func_cmd])
        return (*g_func_array[func_cmd])(file, uarg);

    return -EINVAL;
}

設(shè)置tokenid至進程上下文的token字段并保存:
current即為task_struct數(shù)據(jù)結(jié)構(gòu)

int access_tokenid_set_tokenid(struct file *file, void __user *uarg)
{
    unsigned long long tmp = 0;

    if (!check_permission_for_set_tokenid(file))
        return -EPERM;

    if (copy_from_user(&tmp, uarg, sizeof(tmp)))
        return -EFAULT;

    current->token = tmp;
    return 0;
}

在設(shè)置前做了一個簡單的安全校驗check_permission_for_set_tokenid,只有root權(quán)限進程才可以設(shè)置

access_token在內(nèi)核的使用:
鴻蒙對內(nèi)核新增加CONFIG_ACCESS_TOKENID宏控制此feature的使能
可以看到當前版本主要是在binder,文件系統(tǒng)和創(chuàng)建進程中調(diào)用,目前版本邏輯不多,
只要是賦值和保存

./include/linux/sched.h:1479:#ifdef CONFIG_ACCESS_TOKENID
./fs/proc/base.c:3311:#ifdef CONFIG_ACCESS_TOKENID
./fs/proc/base.c:3318:#endif /* CONFIG_ACCESS_TOKENID */
./fs/proc/base.c:3436:#ifdef CONFIG_ACCESS_TOKENID
./fs/proc/base.c:3771:#ifdef CONFIG_ACCESS_TOKENID
./drivers/Makefile:195:obj-$(CONFIG_ACCESS_TOKENID) += accesstokenid/
./drivers/android/binder.c:98:#ifdef CONFIG_ACCESS_TOKENID
./drivers/android/binder.c:102:#endif /* CONFIG_ACCESS_TOKENID */
./drivers/android/binder.c:558:#ifdef CONFIG_ACCESS_TOKENID
./drivers/android/binder.c:560:#endif /* CONFIG_ACCESS_TOKENID */
./drivers/android/binder.c:606:#ifdef CONFIG_ACCESS_TOKENID
./drivers/android/binder.c:609:#endif /* CONFIG_ACCESS_TOKENID */
./drivers/android/binder.c:3107:#ifdef CONFIG_ACCESS_TOKENID
./drivers/android/binder.c:3110:#endif /* CONFIG_ACCESS_TOKENID */
./drivers/android/binder.c:4560:#ifdef CONFIG_ACCESS_TOKENID
./drivers/android/binder.c:4565:#endif /* CONFIG_ACCESS_TOKENID */
./drivers/android/binder.c:5167:#ifdef CONFIG_ACCESS_TOKENID
./drivers/android/binder.c:5190:#endif /* CONFIG_ACCESS_TOKENID */
./drivers/accesstokenid/Makefile:2:obj-$(CONFIG_ACCESS_TOKENID)     += access_tokenid.o
./kernel/fork.c:877:#ifdef CONFIG_ACCESS_TOKENID
上層使用及相關(guān)邏輯

應(yīng)用hap
在應(yīng)用安裝時,需要調(diào)用AllocHapToken創(chuàng)建獲取該應(yīng)用的TokenID。
在應(yīng)用運行過程中,需要進行鑒權(quán)等操作時,可調(diào)用VerifyAccessToken、GetReqPermissions等函數(shù)查詢校驗應(yīng)用權(quán)限、APL等信息。
在應(yīng)用卸載時,需要調(diào)用DeleteToken函數(shù)刪除系統(tǒng)中管理的對應(yīng)Accesstoken信息。

應(yīng)用安裝時調(diào)用ATM服務(wù)生成AccessTokenId

AccessToken::AccessTokenID BundlePermissionMgr::CreateAccessTokenId(
    const InnerBundleInfo &innerBundleInfo, const std::string bundleName, const int32_t userId)
{
    APP_LOGD("BundlePermissionMgr::CreateAccessTokenId bundleName = %{public}s, userId = %{public}d",
        bundleName.c_str(), userId);
    AccessToken::HapInfoParams hapInfo;
    hapInfo.userID = userId;
    hapInfo.bundleName = bundleName;
    hapInfo.instIndex = 0;
    hapInfo.appIDDesc = innerBundleInfo.GetProvisionId();
    AccessToken::HapPolicyParams hapPolicy = CreateHapPolicyParam(innerBundleInfo);
    AccessToken::AccessTokenIDEx accessToken = AccessToken::AccessTokenKit::AllocHapToken(hapInfo, hapPolicy);
    APP_LOGD("BundlePermissionMgr::CreateAccessTokenId accessTokenId = %{public}u",
             accessToken.tokenIdExStruct.tokenID);
    return accessToken.tokenIdExStruct.tokenID;
}

調(diào)用ATM服務(wù)接口

AccessTokenIDEx AccessTokenManagerService::AllocHapToken(const HapInfoParcel& info, const HapPolicyParcel& policy)
{
    ACCESSTOKEN_LOG_INFO(LABEL, "%{public}s called", __func__);
    AccessTokenIDEx tokenIdEx;
    tokenIdEx.tokenIDEx = 0LL;

    int ret = AccessTokenInfoManager::GetInstance().CreateHapTokenInfo(
        info.hapInfoParameter, policy.hapPolicyParameter, tokenIdEx);
    if (ret != RET_SUCCESS) {
        ACCESSTOKEN_LOG_INFO(LABEL, "hap token info create failed");
    }
    return tokenIdEx;
}

針對Hap包應(yīng)用信息生成AccessToken主要接口

int AccessTokenInfoManager::CreateHapTokenInfo(
    const HapInfoParams& info, const HapPolicyParams& policy, AccessTokenIDEx& tokenIdEx)
{
    if (!DataValidator::IsUserIdValid(info.userID) || !DataValidator::IsBundleNameValid(info.bundleName)
        || !DataValidator::IsAppIDDescValid(info.appIDDesc) || !DataValidator::IsDomainValid(policy.domain)) {
        ACCESSTOKEN_LOG_ERROR(LABEL, "hap token param failed");
        return RET_FAILED;
    }

    AccessTokenID tokenId = AccessTokenIDManager::GetInstance().CreateAndRegisterTokenId(TOKEN_HAP);
    if (tokenId == 0) {
        ACCESSTOKEN_LOG_INFO(LABEL, "token Id create failed");
        return RET_FAILED;
    }

    std::shared_ptr<HapTokenInfoInner> tokenInfo = std::make_shared<HapTokenInfoInner>(tokenId, info, policy);
    if (tokenInfo == nullptr) {
        AccessTokenIDManager::GetInstance().ReleaseTokenId(tokenId);
        ACCESSTOKEN_LOG_INFO(LABEL, "alloc token info failed");
        return RET_FAILED;
    }

    int ret = AddHapTokenInfo(tokenInfo);
    if (ret != RET_SUCCESS) {
        ACCESSTOKEN_LOG_WARN(LABEL, "%{public}s add token info failed", info.bundleName.c_str());
        AccessTokenIDManager::GetInstance().ReleaseTokenId(tokenId);
        return RET_FAILED;
    }
    ACCESSTOKEN_LOG_INFO(LABEL,
        "create hap token 0x%{public}x bundle name %{public}s user %{public}d inst %{public}d ok!",
        tokenId, tokenInfo->GetBundleName().c_str(), tokenInfo->GetUserID(), tokenInfo->GetInstIndex());

    tokenIdEx.tokenIdExStruct.tokenID = tokenId;
    tokenIdEx.tokenIdExStruct.tokenAttr = 0;
    RefreshTokenInfoIfNeeded();
    return RET_SUCCESS;
}

AccessTokenID AccessTokenIDManager::CreateAndRegisterTokenId(ATokenTypeEnum type)
{
    AccessTokenID tokenId = 0;
    // random maybe repeat, retry twice.
    for (int i = 0; i < MAX_CREATE_TOKEN_ID_RETRY; i++) {
        tokenId = CreateTokenId(type);
        if (tokenId == 0) {
            ACCESSTOKEN_LOG_WARN(LABEL, "create tokenId failed");
            return 0;
        }

        int ret = RegisterTokenId(tokenId, type);
        if (ret == RET_SUCCESS) {
            break;
        } else if (i == MAX_CREATE_TOKEN_ID_RETRY - 1) {
            ACCESSTOKEN_LOG_INFO(LABEL, "reigster tokenId failed, maybe repeat, retry");
        } else {
            ACCESSTOKEN_LOG_WARN(LABEL, "reigster tokenId finally failed");
        }
    }
    return tokenId;
}

創(chuàng)建進程時寫入內(nèi)核

void AppSpawnServer::SetAppAccessToken(const ClientSocket::AppProperty *appProperty)
{
    int32_t ret = SetSelfTokenID(appProperty->accessTokenId);
    if (ret != 0) {
        HiLog::Error(LABEL, "AppSpawnServer::Failed to set access token id, errno = %{public}d", errno);
    }
#ifdef WITH_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);
    }
#endif
}

調(diào)用及檢查權(quán)限:

如包管理中檢查權(quán)限
foundation/appexecfwk/standard/services/bundlemgr/src/bundle_permission_mgr.cpp:554:    int32_t ret = AccessToken::AccessTokenKit::VerifyAccessToken(callerToken, permissionName);
foundation/appexecfwk/standard/services/bundlemgr/src/bundle_permission_mgr.cpp:743:    int32_t ret = AccessToken::AccessTokenKit::VerifyAccessToken(tokenId, permissionName);

bool BundlePermissionMgr::VerifyCallingPermission(const std::string &permissionName)
{
    APP_LOGD("VerifyCallingPermission permission %{public}s", permissionName.c_str());
    AccessToken::AccessTokenID callerToken = IPCSkeleton::GetCallingTokenID();
    APP_LOGD("callerToken : %{public}u", callerToken);
    AccessToken::ATokenTypeEnum tokenType = AccessToken::AccessTokenKit::GetTokenTypeFlag(callerToken);
    if (tokenType == AccessToken::ATokenTypeEnum::TOKEN_NATIVE) {
        APP_LOGD("caller tokenType is native, verify success");
        return true;
    }
    int32_t ret = AccessToken::AccessTokenKit::VerifyAccessToken(callerToken, permissionName);
    if (ret == AccessToken::PermissionState::PERMISSION_DENIED) {
        APP_LOGE("permission %{public}s: PERMISSION_DENIED", permissionName.c_str());
        return false;
    }
    APP_LOGD("verify AccessToken success");
    return true;
}

根據(jù)tokenID和permissionName,最終調(diào)用PermissionManager權(quán)限模塊進行驗證權(quán)限是否定義存在等,進行權(quán)限檢查,最后回到AccessTokenInfoManager中根據(jù)tokenid找到某個應(yīng)用應(yīng)用權(quán)限管理策略中集合,并判斷某個權(quán)限的情況并返回

int AccessTokenManagerService::VerifyAccessToken(AccessTokenID tokenID, const std::string& permissionName)
{
    ACCESSTOKEN_LOG_INFO(LABEL,
        "%{public}s called, tokenID: 0x%{public}x, permissionName: %{public}s", __func__,
        tokenID, permissionName.c_str());
    return PermissionManager::GetInstance().VerifyAccessToken(tokenID, permissionName);
}

int PermissionManager::VerifyAccessToken(AccessTokenID tokenID, const std::string& permissionName)
{
    ACCESSTOKEN_LOG_INFO(LABEL, "%{public}s called, tokenID: 0x%{public}x, permissionName: %{public}s", __func__,
        tokenID, permissionName.c_str());
    if (!PermissionValidator::IsPermissionNameValid(permissionName)) {
        ACCESSTOKEN_LOG_ERROR(LABEL, "invalid params!");
        return PERMISSION_DENIED;
    }
    std::shared_ptr<HapTokenInfoInner> tokenInfoPtr =
        AccessTokenInfoManager::GetInstance().GetHapTokenInfoInner(tokenID);
    if (tokenInfoPtr == nullptr) {
        ACCESSTOKEN_LOG_ERROR(LABEL, "can not find tokenInfo!");
        return PERMISSION_DENIED;
    }

    if (!tokenInfoPtr->IsRemote() && !PermissionDefinitionCache::GetInstance().HasDefinition(permissionName)) {
        ACCESSTOKEN_LOG_ERROR(
            LABEL, "no definition for permission: %{public}s!", permissionName.c_str());
        return PERMISSION_DENIED;
    }
    std::shared_ptr<PermissionPolicySet> permPolicySet =
        AccessTokenInfoManager::GetInstance().GetHapPermissionPolicySet(tokenID);
    if (permPolicySet == nullptr) {
        ACCESSTOKEN_LOG_ERROR(LABEL, "invalid params!");
        return PERMISSION_DENIED;
    }

    return permPolicySet->VerifyPermissStatus(permissionName);
}

看到這里可能有些疑問
tokenID和應(yīng)用程序Hap中權(quán)限管理的關(guān)系是什么?

通過以下代碼看出,應(yīng)用的HapTokenInfo中擁有字段permPolicySet_,而permPolicySet_是通過tokenID和應(yīng)用的權(quán)限集合等參數(shù)生成的對象

HapTokenInfoInner::HapTokenInfoInner(AccessTokenID id,
    const HapInfoParams &info, const HapPolicyParams &policy) : isRemote_(false)
{
    tokenInfoBasic_.tokenID = id;
    tokenInfoBasic_.userID = info.userID;
    tokenInfoBasic_.ver = DEFAULT_TOKEN_VERSION;
    tokenInfoBasic_.tokenAttr = 0;
    tokenInfoBasic_.bundleName = info.bundleName;
    tokenInfoBasic_.instIndex = info.instIndex;
    tokenInfoBasic_.appID = info.appIDDesc;
    tokenInfoBasic_.deviceID = "0";
    tokenInfoBasic_.apl = policy.apl;
    permPolicySet_ = PermissionPolicySet::BuildPermissionPolicySet(id, policy.permList, policy.permStateList);
}

所以可以根據(jù)應(yīng)用的tokenID,可以判斷應(yīng)用的某個權(quán)限狀態(tài)是允許還是拒絕

回想一下Android中的權(quán)限模型設(shè)計,frameworks層是以App的UID為單位進行權(quán)限判斷
鴻蒙為什么這樣設(shè)計呢?

int CheckNativeDCap(AccessTokenID tokenID, const std::string& dcap); 檢測指定tokenID對應(yīng)的native進程是否具有指定的分布式能力

(AccessToken可以跨設(shè)備,可能與分布式權(quán)限管理能力有關(guān))

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容