linux能力机制

1. Capabilities的主要思想在于分割root用户的特权,即将root的特权分割成不同的能力,每种能力代表一定的特权操作。例如:能力 CAP_SYS_MODULE 表示用户能够加载(或卸载)内核模块的特权操作,而 CAP_SETUID 表示用户能够修改进程。

2. 用户身份的特权操作。在 Capbilities 中,系统将根据进程拥有的能力来进行特权操作的访问控制。在Capilities中,只有进程和可执行文件才具有能力,每个进程拥有三组能力集,分别称为 cap_effective, cap_inheritable, cap_permitted,分别简记为 pE pI pP,其中:
(1) cap_permitted 表示进程所拥有的最大能力集;
(2) cap_effective 表示进程当前可用的能力集,可以看做是 cap_permitted 的一个子集;
(3) cap_inheitable 则表示进程可以传递给其子进程的能力集。
系统根据进程的 cap_effective 能力集进行访问控制,cap_effective 为 cap_permitted 的子集,进程可以通过取消 cap_effective 中的某些能力来放弃进程的一些特权。

3. 可执行文件也拥有三组能力集,对应于进程的三组能力集,分别称为 cap_effective, cap_allowed, cap_forced,分别简记为 fE fI fP,其中:
(1) cap_allowed 表示程序运行时可从原进程的 cap_inheritable 中继承的能力集;
(2) cap_forced 表示运行文件时必须拥有才能完成其服务的能力集;
(3) ap_effective 表示文件开始运行时可以使用的能力。

4. Linux内核从2.2版本开始,就加进的 Capabilities 的概念与机制,并随着版本升高逐步得到改进。在linux中,root权限被分割成以下29中能力:

CAP_CHOWN: 修改文件属主的权限
CAP_DAC_OVERRIDE: 忽略文件的DAC访问限制
CAP_DAC_READ_SEARCH: 忽略文件读及目录搜索的DAC访问限制
CAP_FOWNER:忽略文件属主ID必须和进程用户ID相匹配的限制
CAP_FSETID: 允许设置文件的 setuid 位
CAP_KILL: 允许对不属于自己的进程发送信号
CAP_SETGID: 允许改变进程的组ID
CAP_SETUID: 允许改变进程的用户ID
CAP_SETPCAP: 允许向其他进程转移能力以及删除其他进程的能力
CAP_LINUX_IMMUTABLE: 允许修改文件的 IMMUTABLE 和 APPEND 属性标志
CAP_NET_BIND_SERVICE: 允许绑定到小于1024的端口
CAP_NET_BROADCAST: 允许网络广播和多播访问
CAP_NET_ADMIN: 允许执行网络管理任务
CAP_NET_RAW: 允许使用原始套接字
CAP_IPC_LOCK: 允许锁定共享内存片段
CAP_IPC_OWNER: 忽略IPC所有权检查
CAP_SYS_MODULE: 允许插入和删除内核模块
CAP_SYS_RAWIO: 允许直接访问/devport,/dev/mem,/dev/kmem及原始块设备
CAP_SYS_CHROOT: 允许使用chroot()系统调用
CAP_SYS_PTRACE: 允许跟踪任何进程
CAP_SYS_PACCT: 允许执行进程的BSD式审计
CAP_SYS_ADMIN: 允许执行系统管理任务,如加载或卸载文件系统、设置磁盘配额等
CAP_SYS_BOOT: 允许重新启动系统
CAP_SYS_NICE: a.允许提升自己的优先级和设置其他进程的优先级;b.允许设置自己的RT调度策略和将其它线程设置为RT线程;c.允许设置其它线程的cpu亲和性
CAP_SYS_RESOURCE: 忽略资源限制
CAP_SYS_TIME: 允许改变系统时钟
CAP_SYS_TTY_CONFIG: 允许配置TTY设备
CAP_MKNOD: 允许使用 mknod()系统调用
CAP_LEASE: 允许修改文件锁的 FL_LEASE 标志

5. 可以使用 setcap/getcap 命令行工具进行设置这些权限,可以使用 capset 系统调用设置权限。

if (AID_ROOT == getuid()) {
    LOGI("[%s]setmediaservercapinrootmode,adjustitforthreadRTschedulepolicy",__func__);
    if(-1 == prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0)) {
        LOGW("mediaserverprctlforsetcapsfailed:%s",strerror(errno));
    } else {
        __user_cap_header_struct hdr;
        __user_cap_data_struct data;
        hdr.version = _LINUX_CAPABILITY_VERSION; //setcaps
        hdr.pid = getpid();
        data.effective = ((1 << CAP_SYS_NICE) | (1 << CAP_SETUID) | (1 << CAP_SETGID));
        data.permitted = ((1 << CAP_SYS_NICE) | (1 << CAP_SETUID) | (1 << CAP_SETGID));
        data.inheritable = 0xffffffff;
        if (-1 == capset(&hdr, &data)) {
            LOGW("mediaservercapsettingfailed,%s",strerror(errno));
        }
    }
}

上面代码,把 root 的权限先使用 prctl 进行 keep,然后通过 capset 进行了限制。capset 的限制过程中,将目录访问的权限消除掉了,会导致无法在/data/system下创建文件,做如下修改便可以恢复:

data.effective = ((1 << CAP_SYS_NICE) | (1 << CAP_SETUID) | (1 << CAP_SETGID) | (1<<CAP_DAC_OVERRIDE));
data.permitted = ((1 << CAP_SYS_NICE) | (1 << CAP_SETUID) | (1 << CAP_SETGID) | (1<<CAP_DAC_OVERRIDE));

6. 其它相关命令工具有:chattr  lcap 

7. sched_setscheduler()的 capability 需求
一般子进程会继承父进程的调度策略,在 Linux 2.6.32 之后,可以使用 SCHED_RESET_ON_FORK 按位与参数的方式调用 sched_setscheduler(), 使用之后效果是:如果调用进程使用 SCHED_FIFO 或 SCHED_RR 调度策略,使用 SCHED_RESET_ON_FORK 后fork创建的子进程创建时将会自动重置为 SCHED_OTHER 调度策略;如果调用进程使用负值nice,那么使用 SCHED_RESET_ON_FORK 后 fork 创建的子进程创建时将会自动将其 nice 重置为0。 这个标记激活时只有当进程具有 CAP_SYS_NICE 标记时才能被重置,而这个 CAP_SYS_NICE 标记在使用fork()创建子进程后,在子进程中被禁止。

8. 补充:有两个系统调用用于判断对应的cap属性,man 可以查看。 capget, capset - set/get capabilities of thread(s)。如下,Android代码中判断是否有CAP_SYS_NICE属性。

//vnd/system/core/libcutils/sched_policy_test.cpp
bool hasCapSysNice() {
    __user_cap_header_struct header;
    memset(&header, 0, sizeof(header));
    header.version = _LINUX_CAPABILITY_VERSION_3;

    __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3];
    if (capget(&header, &caps[0])) {
        GTEST_LOG_(WARNING) << "failed to get process capabilities";
        return false;
    }

    auto nice_idx = CAP_TO_INDEX(CAP_SYS_NICE);
    auto nice_mask = CAP_TO_MASK(CAP_SYS_NICE);
    return caps[nice_idx].effective & nice_mask;
}

设置 CAP_SYS_NICE 属性参考:

//摘取自:vnd/frameworks/native/services/vr/performanced/main.cpp
#include <errno.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <sys/stat.h>

namespace {

// Annoying that sys/capability.h doesn't define this directly.
constexpr int kMaxCapNumber = (CAP_TO_INDEX(CAP_LAST_CAP) + 1);

}  // anonymous namespace

int main(int /*argc*/, char** /*argv*/) {
  int ret = -1;

  struct __user_cap_header_struct capheader;
  struct __user_cap_data_struct capdata[kMaxCapNumber];

  ALOGI("Starting up...");

  // We need to be able to create endpoints with full perms.
  umask(0000);

  // Keep capabilities when switching UID to AID_SYSTEM.
  ret = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
  CHECK_ERROR(ret < 0, error, "Failed to set KEEPCAPS: %s", strerror(errno));

  // Set UID and GID to system.
  ret = setresgid(AID_SYSTEM, AID_SYSTEM, AID_SYSTEM);
  CHECK_ERROR(ret < 0, error, "Failed to set GID: %s", strerror(errno));
  ret = setresuid(AID_SYSTEM, AID_SYSTEM, AID_SYSTEM);
  CHECK_ERROR(ret < 0, error, "Failed to set UID: %s", strerror(errno));

  // Keep CAP_SYS_NICE, allowing control of scheduler class, priority, and
  // cpuset for other tasks in the system.
  memset(&capheader, 0, sizeof(capheader));
  memset(&capdata, 0, sizeof(capdata));
  capheader.version = _LINUX_CAPABILITY_VERSION_3;
  capdata[CAP_TO_INDEX(CAP_SYS_NICE)].effective |= CAP_TO_MASK(CAP_SYS_NICE);
  capdata[CAP_TO_INDEX(CAP_SYS_NICE)].permitted |= CAP_TO_MASK(CAP_SYS_NICE);

  // Drop all caps but the ones configured above.
  ret = capset(&capheader, capdata);

  return ret;
}

 

 

参考:
setcap详解:https://www.cnblogs.com/nf01/articles/10418141.html
用capability 特征加强Linux系统安全:https://blog.csdn.net/cuikeng1956/article/details/100400918

 

优秀博文 《Linux的capability深入分析》:https://www.cnblogs.com/iamfy/archive/2012/09/20/2694977.html

 

posted on 2021-09-03 21:13  Hello-World3  阅读(763)  评论(0编辑  收藏  举报

导航