鸿蒙OpenHarmony 安全子系统-ATM(AccessToken)

库代码地址:https://gitee.com/openharmony/security_access_token

以下引用官方介绍:


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

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

创建AccessTokenId
通过随机数创建后保存在
#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进程拉起前,需要调用GetAccessTokenId函数,获取该native进程的TokenID;再调用SetSelfTokenID将进程TokenID设置到内核中。
在native进程运行过程中,可以通过调用GetNativeTokenInfo、CheckNativeDCap来查验对应进程所具备的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);
    }
}

系统调用入口并写入内核
https://gitee.com/openharmony/security_access_token/blob/master/interfaces/innerkits/token_setproc/src/token_setproc.c

设备驱动
#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;
}
内核驱动

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

dev设备驱动入内核层调用入口

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;
}

设置tokenid至进程上下文的token字段并保存:
current即为task_struct数据结构

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;
}

在设置前做了一个简单的安全校验check_permission_for_set_tokenid,只有root权限进程才可以设置

access_token在内核的使用:
鸿蒙对内核新增加CONFIG_ACCESS_TOKENID宏控制此feature的使能
可以看到当前版本主要是在binder,文件系统和创建进程中调用,目前版本逻辑不多,
只要是赋值和保存

./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
上层使用及相关逻辑

应用hap
在应用安装时,需要调用AllocHapToken创建获取该应用的TokenID。
在应用运行过程中,需要进行鉴权等操作时,可调用VerifyAccessToken、GetReqPermissions等函数查询校验应用权限、APL等信息。
在应用卸载时,需要调用DeleteToken函数删除系统中管理的对应Accesstoken信息。

应用安装时调用ATM服务生成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;
}

调用ATM服务接口

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包应用信息生成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;
}

创建进程时写入内核

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
}

调用及检查权限:

如包管理中检查权限
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;
}

根据tokenID和permissionName,最终调用PermissionManager权限模块进行验证权限是否定义存在等,进行权限检查,最后回到AccessTokenInfoManager中根据tokenid找到某个应用应用权限管理策略中集合,并判断某个权限的情况并返回

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和应用程序Hap中权限管理的关系是什么?

通过以下代码看出,应用的HapTokenInfo中拥有字段permPolicySet_,而permPolicySet_是通过tokenID和应用的权限集合等参数生成的对象

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);
}

所以可以根据应用的tokenID,可以判断应用的某个权限状态是允许还是拒绝

回想一下Android中的权限模型设计,frameworks层是以App的UID为单位进行权限判断
鸿蒙为什么这样设计呢?

int CheckNativeDCap(AccessTokenID tokenID, const std::string& dcap); 检测指定tokenID对应的native进程是否具有指定的分布式能力

(AccessToken可以跨设备,可能与分布式权限管理能力有关)

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

推荐阅读更多精彩内容