1关键字

触屏;caps;mmi子系统

 

2 问题描述

开发板型号:Firefly ROC-RK3568-PC

内核版本: Firefly 4.19

问题现象:firefly3568的板子适配3.1release分支的OH,重启后,触摸屏触屏无效

测试步骤:

1.修改挂载路径和分区表,重新编译代码,获得vendor.img,sys term.img,userdata.img和parameter.txt

2.将上述获取文件烧进firefly3568

3.按下开发板的reboot按钮

4.触摸屏正常亮屏后点击任意应用

5.期待结果是进入点击的应用界面,但是无响应

 

3 问题原因

3.1 正常机制

mmi子系统启动前,会对mmi子系统的配置文件进行解析,解析获取的数据会在服务启动时进行属性检测,检测通过就会正常启动

 

3.2 异常机制

解析mmi配置文件时,获取了错误的caps值,服务启动检验caps值不通过,服务重启四次后终止启动

 

4 解决方案

配置文件路径为:\foundation\multimodalinput\input\multimodalinput.cfg

将错误的配置字段 "caps" : ["CAP_DAC_READ_SEARCH", "CAP_DC_OVERRIDE", "CAP_CHOWN"]

修改为正确配置如下:

"caps" : ["DAC_READ_SEARCH", "DAC_OVERRIDE", "CHOWN"]

正确的配置参考下面函数定义的字符串

static unsigned int GetCapByString(const char *capStr)
{
  static const CapStrCapNum capStrCapNum[] = {
    { "CHOWN", CAP_CHOWN },
    { "DAC_OVERRIDE", CAP_DAC_OVERRIDE },
    { "DAC_READ_SEARCH", CAP_DAC_READ_SEARCH },
    { "FOWNER", CAP_FOWNER },
    { "FSETID", CAP_FSETID },
    { "KILL", CAP_KILL },
    { "SETGID", CAP_SETGID },
    { "SETUID", CAP_SETUID },
    { "SETPCAP", CAP_SETPCAP },
    { "LINUX_IMMUTABLE", CAP_LINUX_IMMUTABLE },
    { "NET_BIND_SERVICE", CAP_NET_BIND_SERVICE },
    { "NET_BROADCAST", CAP_NET_BROADCAST },
    { "NET_ADMIN", CAP_NET_ADMIN },
    { "NET_RAW", CAP_NET_RAW },
    { "IPC_LOCK", CAP_IPC_LOCK },
    { "IPC_OWNER", CAP_IPC_OWNER },
    { "SYS_MODULE", CAP_SYS_MODULE },
    { "SYS_RAWIO", CAP_SYS_RAWIO },
    { "SYS_CHROOT", CAP_SYS_CHROOT },
    { "SYS_PTRACE", CAP_SYS_PTRACE },
    { "SYS_PACCT", CAP_SYS_PACCT },
    { "SYS_ADMIN", CAP_SYS_ADMIN },
    { "SYS_BOOT", CAP_SYS_BOOT },
    { "SYS_NICE", CAP_SYS_NICE },
    { "SYS_RESOURCE", CAP_SYS_RESOURCE },
    { "SYS_TIME", CAP_SYS_TIME },
    { "SYS_TTY_CONFIG", CAP_SYS_TTY_CONFIG },
    { "MKNOD", CAP_MKNOD },
    { "LEASE", CAP_LEASE },
    { "AUDIT_WRITE", CAP_AUDIT_WRITE },
    { "AUDIT_CONTROL", CAP_AUDIT_CONTROL },
    { "SETFCAP", CAP_SETFCAP },
    { "MAC_OVERRIDE", CAP_MAC_OVERRIDE },
    { "MAC_ADMIN", CAP_MAC_ADMIN },
    { "SYSLOG", CAP_SYSLOG },
    { "WAKE_ALARM", CAP_WAKE_ALARM },
    { "BLOCK_SUSPEND", CAP_BLOCK_SUSPEND },
    { "AUDIT_READ", CAP_AUDIT_READ },
  };
  int mapSize = (int)ARRAY_LENGTH(capStrCapNum);
  for (int j = 0; j < mapSize; j++) {
    if (strcmp(capStr, capStrCapNum[j].capStr) == 0) {
      return capStrCapNum[j].CapNum;
    }
  }
  return -1;
}

 

5 定位过程

1.在复现后,通过与正常启动的log比较快速定位了这两条异常的log如下图所示

 

 

1.1异常log在代码中的位置如下

base\startup\init_lite\services\init\adapter\init_adapter.c

int SetAmbientCapability(int cap)
​
{
\#if ((defined __LINUX__) || (!defined OHOS_LITE))
  if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0)) {
    INIT_LOGE("prctl PR_CAP_AMBIENT failed: %d", errno);
    return -1;
  }
\#endif
  return 0;
}

1.2这个函数的Ambient特权通过prctl(PR_CAP_AMBIENT…)系统调用操作

 

base\startup\init_lite\services\init\init_common_service.c

int ServiceStart(Service *service)
​
{
 .........................................................
​
    INIT_CHECK_ONLY_ELOG(BindCpuCore(service) == SERVICE_SUCCESS,
      "binding core number failed for service %s", service->name);
    // permissions
    INIT_ERROR_CHECK(SetPerms(service) == SERVICE_SUCCESS, _exit(PROCESS_EXIT_CODE),
      "service %s exit! set perms failed! err %d.", service->name, errno);
  ........................................................
}

1.3服务启动时在这里进行了一系列的属性检测,属性检测不通过就会造成服务启动失败

 

2进入SetPerms函数发现里面有调用SetAmbientCapability,再通过SetAmbientCapability函数带入的参数service->servPerm.caps[i]进行搜索

static int SetPerms(const Service *service)
​
{
...................................
  for (unsigned int i = 0; i < service->servPerm.capsCnt; ++i) {
    if (service->servPerm.caps[i] == FULL_CAP) {
      for (int j = 0; j < CAP_NUM; ++j) {
        capData[j].effective = FULL_CAP;//进程当前可用的能力集,可以看做是cap_permitted的一个子集
        capData[j].permitted = FULL_CAP;//进程所拥有的最大能力集
        capData[j].inheritable = FULL_CAP;//进程可以传递给其子进程的能力集
       break;
    }
    capData[CAP_TO_INDEX(service->servPerm.caps[i])].effective |= CAP_TO_MASK(service->servPerm.caps[i]);
    capData[CAP_TO_INDEX(service->servPerm.caps[i])].permitted |= CAP_TO_MASK(service->servPerm.caps[i]);
    capData[CAP_TO_INDEX(service->servPerm.caps[i])].inheritable |= CAP_TO_MASK(service->servPerm.caps[i]);
  }
  if (capset(&capHeader, capData) != 0) {
    INIT_LOGE("capset faild for service: %s, error: %d", service->name, errno);
    return SERVICE_FAILURE;
  }
  for (unsigned int i = 0; i < service->servPerm.capsCnt; ++i) {
    if (service->servPerm.caps[i] == FULL_CAP) {
      return SetAllAmbientCapability();
      INIT_LOGE("SetAllAmbientCapability: %s failed, uid = %d", service->name, service->servPerm.uID);//
    }
    if (SetAmbientCapability(service->servPerm.caps[i]) != 0) {
      INIT_LOGE("SetAmbientCapability faild for service: %s", service->name);
      return SERVICE_FAILURE;
    }
  }
  return SERVICE_SUCCESS;
}

 

3.通过搜索service->servPerm.caps[i],发现在GetServiceCaps中有被调用,通过这个函数可以看出是caps获取的方式有两种,一种是string一种是number,分别在caps = (unsigned int)cJSON_GetNumberValue(capJson);和caps = GetCapByString(capStr);后面添加了log如下所示

base\startup\init_lite\services\init\init_capability.c

int GetServiceCaps(const cJSON *curArrItem, Service *service)
​
{
  INIT_ERROR_CHECK(service != NULL, return SERVICE_FAILURE, "service is null ptr.");
  INIT_ERROR_CHECK(curArrItem != NULL, return SERVICE_FAILURE, "json is null ptr.");
  service->servPerm.capsCnt = 0;
  service->servPerm.caps = NULL;
  int capsCnt = 0;
  cJSON *filedJ = GetArrayItem(curArrItem, &capsCnt, "caps");
  if (filedJ == NULL) {
    return SERVICE_SUCCESS;
  }
  INIT_ERROR_CHECK(capsCnt <= MAX_CAPS_CNT_FOR_ONE_SERVICE, return SERVICE_FAILURE,
    "service=%s, too many caps[cnt %d] for one service", service->name, capsCnt);
  service->servPerm.caps = (unsigned int *)calloc(1, sizeof(unsigned int) * capsCnt);
  INIT_ERROR_CHECK(service->servPerm.caps != NULL, return SERVICE_FAILURE,
    "Failed to malloc for service %s", service->name);
  service->servPerm.capsCnt = capsCnt;
  unsigned int caps = FULL_CAP;
  for (int i = 0; i < capsCnt; ++i) { // number form
    cJSON *capJson = cJSON_GetArrayItem(filedJ, i);
    if (cJSON_IsNumber(capJson)) { // for number
      caps = (unsigned int)cJSON_GetNumberValue(capJson);
      INIT_LOGI("*****************service0=%s, caps = %d.*********", service->name, caps);
    } else if (cJSON_IsString(capJson)) {
​
      char *capStr = cJSON_GetStringValue(capJson);
      if (capStr == NULL || strlen(capStr) <= 0) { // check all errors
        INIT_LOGE("service=%s, parse item[%d] as string, error.", service->name, i);
        break;
      }
      caps = GetCapByString(capStr);
      INIT_LOGI("*****************service=%s, caps = %d.*********", service->name, caps);
    }
    INIT_CHECK_RETURN_VALUE(caps >= 0, SERVICE_FAILURE);
    if ((caps > CAP_LAST_CAP) && (caps != (unsigned int)FULL_CAP)) {
      INIT_LOGE("service=%s, caps = %d, error.", service->name, caps);
      return SERVICE_FAILURE;
    }
    service->servPerm.caps[i] = (unsigned int)caps;
  }
  return 0;
}

 

4.编译代码烧入开发板截取log显示如下,可以看出是通过GetCapByString(capStr)方法获取的,也可以根据GetCapByString函数看出获取的caps值异常

 

 

5.再搜索GetServiceCaps函数,最终发现是在解析cfg文件的时候会调用到GetServiceCaps,那么我们查看mmi的cfg文件,通过比较GetCapByString函数里定义的字符串和caps配置的字符串,发现caps配置错误,解析该文件时获取了错误的caps值,mmi服务启动时调用setperms,在该函数里会将获取的caps值传入SetAmbientCapability函数里,之后被prctl(PR_CAP_AMBIENT…)系统调用失败,返回错误值,使服务启动失败,造成触屏无效

{
  "jobs" : [{
      "name" : "post-fs",
      "cmds" : [
        "start udevd_service",
        "sleep 1",
        "start mmi_uinput_service",
        "exec /system/bin/udevadm trigger",
        "sleep 2",
        "start multimodalinput"
      ]
    }
  ],
  "services" : [{
      "name" : "multimodalinput",
      "path" : ["/system/bin/sa_main", "/system/profile/multimodalinput.xml"],
      "uid" : "system",
      "gid" : ["system", "shell", "uhid"],
      "caps" : ["CAP_DAC_READ_SEARCH", "CAP_DC_OVERRIDE", "CAP_CHOWN"],#当前服务所需的capability值,根据安全子系统已支持的capability,评估所需的capability,遵循最小权限原则配置。
      "start-mode" : "condition"
    }, {
      "name" : "udevd_service",
      "path" : ["/system/bin/udevd"],
      "uid" : "root",
      "gid" : ["system"],
      "start-mode" : "condition"
    }, {
      "name" : "mmi_uinput_service",
      "path" : ["/system/bin/uinput_inject"],
      "uid" : "uhid",
      "gid" : ["system", "shell", "uhid"],
      "caps" : ["CAP_DAC_READ_SEARCH", "CAP_DC_OVERRIDE", "CAP_CHOWN"],
      "start-mode" : "condition"
    }
  ]
}

 

6 知识分享

  • Capabilities 机制是在 Linux 内核 2.2 之后引入的,原理是将之前与超级用户 root(UID=0)关联的特权细分为不同的功能组,Capabilites 作为线程(Linux 并不真正区分进程和线程)的属性存在,每个功能组都可以独立启用和禁用,其本质上就是将内核调用分门别类,具有相似功能的内核调用被分到同一组中。可参考http://3ms.huawei.com/km/blogs/details/8522121

  • Ambient Capability是一种特权继承的方法,是在kernel 4.3才引入,老版本内核不支持,使用方式简洁,动态配置,不依赖文件系统xattr。可参考http://3ms.huawei.com/km/blogs/details/6041907

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐