SEAndroid流程分析

  Init进程对Android启动的selinux环境作了初始化。

/system/core/init/init.cpp

    // Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
    selinux_initialize(is_first_stage);

  init第一阶段的selinux初始化主要是加载sepolicy文件。

/system/core/init/init.cpp

static void selinux_initialize(bool in_kernel_domain) {
    Timer t;
    //设置回调打印kernel log的函数和设置property时的审计信息写入函数
    selinux_callback cb;
    cb.func_log = selinux_klog_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);
    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);
    ///sys/fs/selinux不可读或者内核启动参数/proc/cmdline的"androidboot.selinux"值      为"disabled"时,selinux为关闭状态,直接返回
    if (selinux_is_disabled()) {
        return;
    }
    //init第一阶段
    if (in_kernel_domain) {
        INFO("Loading SELinux policy...\n");
        //初始化sepolicy相关文件
        if (selinux_android_load_policy() < 0) {
            ERROR("failed to load policy: %s\n", strerror(errno));
            security_failure();
        }

        //is_enforcing为true表示强制开启
        bool is_enforcing = selinux_is_enforcing();
        security_setenforce(is_enforcing);

        if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) {
            security_failure();
        }

        NOTICE("(Initializing SELinux %s took %.2fs.)\n",
               is_enforcing ? "enforcing" : "non-enforcing", t.duration());
    } else {//init第二阶段
        selinux_init_all_handles();
    }
}

/external/libselinux/src/android.c

int selinux_android_load_policy(void)
{
    //挂载selinuxfs到/sys/fs/selinux上
    const char *mnt = SELINUXMNT;
    int rc;
    rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
    if (rc < 0) {
        if (errno == ENODEV) {
            /* SELinux not enabled in kernel */
            return -1;
        }
        if (errno == ENOENT) {
            /* Fall back to legacy mountpoint. */
            mnt = OLDSELINUXMNT;
            rc = mkdir(mnt, 0755);
            if (rc == -1 && errno != EEXIST) {
                selinux_log(SELINUX_ERROR,"SELinux:  Could not mkdir:  %s\n",
                    strerror(errno));
                return -1;
            }
            rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
        }
    }
    if (rc < 0) {
        selinux_log(SELINUX_ERROR,"SELinux:  Could not mount selinuxfs:  %s\n",
                strerror(errno));
        return -1;
    }
    //保存挂载目录/sys/fs/selinux到selinux_mnt中
    set_selinuxmnt(mnt);

    return selinux_android_load_policy_helper(false);
}

  sepolicy保存着kernel selinux模块需要读取的策略文件,可以在源码的/external/sepolicy目录下找到这些te文件。

/external/libselinux/src/android.c

static int selinux_android_load_policy_helper(bool reload)
{
    int fd = -1, rc;
    struct stat sb;
    void *map = NULL;
    int old_policy_index = policy_index;

    /*
     * If reloading policy and there is no /data policy or
     * that /data policy has the wrong version and our prior
     * load was from the / policy, then just return.
     * There is no point in reloading policy from / a second time.
     */

     //根据/data/security/current/selinux_version与/selinux_version内容是否完全相同决定下面要使用到的文件名,简单点就是将policy_index设为0。
    set_policy_index();
    if (reload && !policy_index && !old_policy_index)
        return 0;
    //打开/sepolicy文件
    fd = open(sepolicy_file[policy_index], O_RDONLY | O_NOFOLLOW);
    if (fd < 0) {
        selinux_log(SELINUX_ERROR, "SELinux:  Could not open sepolicy:  %s\n",
                strerror(errno));
        return -1;
    }
    if (fstat(fd, &sb) < 0) {
        selinux_log(SELINUX_ERROR, "SELinux:  Could not stat %s:  %s\n",
                sepolicy_file[policy_index], strerror(errno));
        close(fd);
        return -1;
    }
    map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (map == MAP_FAILED) {
        selinux_log(SELINUX_ERROR, "SELinux:  Could not map %s:  %s\n",
                sepolicy_file[policy_index], strerror(errno));
        close(fd);
        return -1;
    }
    //将/sepolicy的内容写入到/sys/fs/selinux/load供给kernel的selinux模块读取
    rc = security_load_policy(map, sb.st_size);
    if (rc < 0) {
        selinux_log(SELINUX_ERROR, "SELinux:  Could not load policy:  %s\n",
                strerror(errno));
        munmap(map, sb.st_size);
        close(fd);
        return -1;
    }

    munmap(map, sb.st_size);
    close(fd);
    selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", sepolicy_file[policy_index]);

    return 0;
}

int selinux_android_reload_policy(void)
{
    return selinux_android_load_policy_helper(true);
}

int selinux_android_load_policy(void)
{
    const char *mnt = SELINUXMNT;
    int rc;
    rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
    if (rc < 0) {
        if (errno == ENODEV) {
            /* SELinux not enabled in kernel */
            return -1;
        }
        if (errno == ENOENT) {
            /* Fall back to legacy mountpoint. */
            mnt = OLDSELINUXMNT;
            rc = mkdir(mnt, 0755);
            if (rc == -1 && errno != EEXIST) {
                selinux_log(SELINUX_ERROR,"SELinux:  Could not mkdir:  %s\n",
                    strerror(errno));
                return -1;
            }
            rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
        }
    }
    if (rc < 0) {
        selinux_log(SELINUX_ERROR,"SELinux:  Could not mount selinuxfs:  %s\n",
                strerror(errno));
        return -1;
    }
    set_selinuxmnt(mnt);

    return selinux_android_load_policy_helper(false);
}

  可以看到,selinux初始化在init第一阶段的工作是让kernel去加载sepolicy策略文件。现在看看selinux初始化在init第二阶段的工作吧:

/system/core/init/init.cpp

static void selinux_init_all_handles(void)
{
    //初始化文件安全上下文
    sehandle = selinux_android_file_context_handle();
    selinux_android_set_sehandle(sehandle);
    //初始化属性安全上下文
    sehandle_prop = selinux_android_prop_context_handle();
}

  先看看selinux_android_file_context_handle。selabel_open主要功能使初始化
struct selabel_handle*类型的sehandle。

struct selabel_handle* selinux_android_file_context_handle(void)
{
    struct selabel_handle *sehandle;

    set_policy_index();
    sehandle = selabel_open(SELABEL_CTX_FILE, &seopts[policy_index], 1);

    if (!sehandle) {
        selinux_log(SELINUX_ERROR, "%s: Error getting file context handle (%s)\n",
                __FUNCTION__, strerror(errno));
        return NULL;
    }
    if (!compute_contexts_hash(seopts, fc_digest)) {
        selabel_close(sehandle);
        return NULL;
    }
    selinux_log(SELINUX_INFO, "SELinux: Loaded file_contexts contexts from %s.\n",
            seopts[policy_index].value);

    return sehandle;
}

  末尾调用selabel_file_init函数。

/external/libselinux/src/label.c

struct selabel_handle *selabel_open(unsigned int backend,
                    const struct selinux_opt *opts,
                    unsigned nopts)
{
    struct selabel_handle *rec = NULL;

    if (backend >= ARRAY_SIZE(initfuncs)) {
        errno = EINVAL;
        goto out;
    }

    if (initfuncs[backend] == NULL)
        goto out;

    rec = (struct selabel_handle *)malloc(sizeof(*rec));
    if (!rec)
        goto out;

    memset(rec, 0, sizeof(*rec));
    rec->backend = backend;
    rec->validating = selabel_is_validate_set(opts, nopts);

    if ((*initfuncs[backend])(rec, opts, nopts)) {
        free(rec);
        rec = NULL;
    }

out:
    return rec;
}

  这里初始化struct selabel_handle的一些功能函数,为保存配置的区域struct saved_data *data分配了空间,最后调用init进行data的初始化。

/external/libselinux/src/label_file.c

int selabel_file_init(struct selabel_handle *rec, const struct selinux_opt *opts,
              unsigned nopts)
{
    struct saved_data *data;

    data = (struct saved_data *)malloc(sizeof(*data));
    if (!data)
        return -1;
    memset(data, 0, sizeof(*data));

    rec->data = data;
    rec->func_close = &closef;
    rec->func_stats = &stats;
    rec->func_lookup = &lookup;
    rec->func_partial_match = &partial_match;
    rec->func_lookup_best_match = &lookup_best_match;

    return init(rec, opts, nopts)

  init会解析/file_context文件的内容,将相关信息填充到入参rec的data成员中。填充过程会分为两次进行,第一次是计算安全上下文信息数量(spec_t的数量)及处理一些认证工作。第二次会依次关联安全上下文信息(spec_t)和根文件名信息(stem_t),保存regex信息和context信息和确保没有重复的安全上下文信息(spec_t)。这些安全上下文信息(spec_t)会被保存在数组data->spec_arr中,根文件信息(stem_t)会被保存在数组data->stem_arr中。

/external/libselinux/src/label_file.c

static int init(struct selabel_handle *rec, const struct selinux_opt *opts,
        unsigned n)
{
    struct saved_data *data = (struct saved_data *)rec->data;
    const char *path = NULL;
    const char *prefix = NULL;
    FILE *fp;
    FILE *localfp = NULL;
    FILE *homedirfp = NULL;
    char local_path[PATH_MAX + 1];
    char homedir_path[PATH_MAX + 1];
    char line_buf[BUFSIZ];
    unsigned int lineno, pass, i, j, maxnspec;
    spec_t *spec_copy = NULL;
    int status = -1, baseonly = 0;
    struct stat sb;

    /* Process arguments */
    //这里n为1,进入到case SELABEL_OPT_PATH的逻辑中,使得path为"/file_contexts"
    while (n--)
        switch(opts[n].type) {
        case SELABEL_OPT_PATH:
            path = opts[n].value;
            break;
        case SELABEL_OPT_SUBSET:
            prefix = opts[n].value;
            break;
        case SELABEL_OPT_BASEONLY:
            baseonly = !!opts[n].value;
            break;
        }

    /* Open the specification file. */
    if ((fp = fopen(path, "r")) == NULL)
        return -1;

    if (fstat(fileno(fp), &sb) < 0)
        return -1;
    if (!S_ISREG(sb.st_mode)) {
        errno = EINVAL;
        return -1;
    }

    if (!baseonly) {
        snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs",
             path);
        homedirfp = fopen(homedir_path, "r");

        snprintf(local_path, sizeof(local_path), "%s.local", path);
        localfp = fopen(local_path, "r");
    }

    /* 
     * Perform two passes over the specification file.
     * The first pass counts the number of specifications and
     * performs simple validation of the input.  At the end
     * of the first pass, the spec array is allocated.
     * The second pass performs detailed validation of the input
     * and fills in the spec array.
     */
    maxnspec = UINT_MAX / sizeof(spec_t);
        //文件中的行数
        lineno = 0;
        //文件中安全上下文信息条数
        data->nspec = 0;
        data->ncomp = 0;
        //读取/file_contexts文件的每一行
        while (fgets(line_buf, sizeof line_buf - 1, fp)
               && data->nspec < maxnspec) {
            //解析文件内容
            if (process_line(rec, path, prefix, line_buf,
                     pass, ++lineno) != 0)
                goto finish;
        }
        //判断没有重复的安全上下文信息
        if (pass == 1) {
            status = nodups_specs(data, path);
            if (status)
                goto finish;
        }
        lineno = 0;
        if (homedirfp)
            while (fgets(line_buf, sizeof line_buf - 1, homedirfp)
                   && data->nspec < maxnspec) {
                if (process_line
                    (rec, homedir_path, prefix,
                     line_buf, pass, ++lineno) != 0)
                    goto finish;
            }

        lineno = 0;
        if (localfp)
            while (fgets(line_buf, sizeof line_buf - 1, localfp)
                   && data->nspec < maxnspec) {
                if (process_line
                    (rec, local_path, prefix, line_buf,
                     pass, ++lineno) != 0)
                    goto finish;
            }

        if (pass == 0) {
            if (data->nspec == 0) {
                status = 0;
                goto finish;
            }
            if (NULL == (data->spec_arr =
                     (spec_t *) malloc(sizeof(spec_t) * data->nspec)))
                goto finish;
            memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec);
            maxnspec = data->nspec;
            rewind(fp);
            if (homedirfp)
                rewind(homedirfp);
            if (localfp)
                rewind(localfp);
        }
    }

    /* Move exact pathname specifications to the end. */
    //重新排序,将有meta-chars的放在spec_arr前面,没有的放在后面
    spec_copy = (spec_t *) malloc(sizeof(spec_t) * data->nspec);
    if (!spec_copy)
        goto finish;
    j = 0;
    for (i = 0; i < data->nspec; i++)
        if (data->spec_arr[i].hasMetaChars)
            memcpy(&spec_copy[j++],
                   &data->spec_arr[i], sizeof(spec_t));
    for (i = 0; i < data->nspec; i++)
        if (!data->spec_arr[i].hasMetaChars)
            memcpy(&spec_copy[j++],
                   &data->spec_arr[i], sizeof(spec_t));
    free(data->spec_arr);
    data->spec_arr = spec_copy;

    status = 0;
finish:
    fclose(fp);
    if (data->spec_arr != spec_copy)
        free(data->spec_arr);
    if (homedirfp)
        fclose(homedirfp);
    if (localfp)
        fclose(localfp);
    return status;
}

/external/libselinux/src/label_file.c

static int process_line(struct selabel_handle *rec,
            const char *path, const char *prefix,
            char *line_buf, int pass, unsigned lineno)
{
    int items, len;
    char buf1[BUFSIZ], buf2[BUFSIZ], buf3[BUFSIZ];
    char *buf_p, *regex = buf1, *type = buf2, *context = buf3;
    struct saved_data *data = (struct saved_data *)rec->data;
    spec_t *spec_arr = data->spec_arr;
    unsigned int nspec = data->nspec;

    len = strlen(line_buf);
    if (line_buf[len - 1] == '\n')
        line_buf[len - 1] = 0;
    buf_p = line_buf;
    while (isspace(*buf_p))
        buf_p++;
    /* Skip comment lines and empty lines. */
    if (*buf_p == '#' || *buf_p == 0)
        return 0;
    //以tab为分隔符,将一行的内容分成三部分保存在regex, type, context中
    items = sscanf(line_buf, "%255s %255s %255s", regex, type, context);
    if (items < 2) {
        selinux_log(SELINUX_WARNING,
                "%s:  line %d is missing fields, skipping\n", path,
                lineno);
        return 0;
    } else if (items == 2) {//有些行会没有type,只有regex和context。这里默认都按这种情况处理
        /* The type field is optional. */
        context = type;
        type = NULL;
    }
    //获取regex部分的根文件名长度
    len = get_stem_from_spec(regex);
    if (len && prefix && strncmp(prefix, regex, len)) {
        /* Stem of regex does not match requested prefix, discard. */
        return 0;
    }

    if (pass == 1) {
        /* On the second pass, process and store the specification in spec. */
        const char *errbuf = NULL;
        //查找对应的stem_t位置,与spec_t关联
        spec_arr[nspec].stem_id = find_stem_from_spec(data, regex);
        spec_arr[nspec].regex_str = strdup(regex);
        if (!spec_arr[nspec].regex_str) {
            selinux_log(SELINUX_WARNING,
                   "%s:  out of memory at line %d on regex %s\n",
                   path, lineno, regex);
            return -1;

        }
        if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) {
            selinux_log(SELINUX_WARNING,
                   "%s:  line %d has invalid regex %s:  %s\n",
                   path, lineno, regex,
                   (errbuf ? errbuf : "out of memory"));
        }

        /* Convert the type string to a mode format */
        spec_arr[nspec].mode = 0;
        //这里type为null
        if (!type)
            goto skip_type;
        spec_arr[nspec].type_str = strdup(type);
        len = strlen(type);
        if (type[0] != '-' || len != 2) {
            selinux_log(SELINUX_WARNING,
                    "%s:  line %d has invalid file type %s\n",
                    path, lineno, type);
            return 0;
        }
        switch (type[1]) {
        case 'b':
            spec_arr[nspec].mode = S_IFBLK;
            break;
        case 'c':
            spec_arr[nspec].mode = S_IFCHR;
            break;
        case 'd':
            spec_arr[nspec].mode = S_IFDIR;
            break;
        case 'p':
            spec_arr[nspec].mode = S_IFIFO;
            break;
        case 'l':
            spec_arr[nspec].mode = S_IFLNK;
            break;
        case 's':
            spec_arr[nspec].mode = S_IFSOCK;
            break;
        case '-':
            spec_arr[nspec].mode = S_IFREG;
            break;
        default:
            selinux_log(SELINUX_WARNING,
                    "%s:  line %d has invalid file type %s\n",
                    path, lineno, type);
            return 0;
        }

    skip_type:
        //保存安全上下文信息
        spec_arr[nspec].lr.ctx_raw = strdup(context);

        if (strcmp(context, "<<none>>") && rec->validating) {
            if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) {
                selinux_log(SELINUX_WARNING,
                        "%s:  line %d has invalid context %s\n",
                        path, lineno, spec_arr[nspec].lr.ctx_raw);
            }
        }

        /* Determine if specification has 
         * any meta characters in the RE */
        //有些regex是以正则表达式的形式出现的,如"/data(/.*)?",这种情况将被视为拥有meta-chars,对应的spec_t的hasMetaChars置1
        spec_hasMetaChars(&spec_arr[nspec]);
    }
    //读取完一行,安全上下文信息数加1
    data->nspec = ++nspec;
    return 0;
}

/external/libselinux/src/label_file.c

static int find_stem_from_spec(struct saved_data *data, const char *buf)
{
    int i, num = data->num_stems;
    //stem_len为regex部分根文件名的长度(如"/data"),若regex部分为正则表达式,则返回0(如"/system(/.*)?")
    int stem_len = get_stem_from_spec(buf);

    if (!stem_len)
        return -1;
    //若stem_arr中找到对应的根文件名信息(stem_t),返回所在位置
    for (i = 0; i < num; i++) {
        if (stem_len == data->stem_arr[i].len
            && !strncmp(buf, data->stem_arr[i].buf, stem_len))
            return i;
    }
    //stem_arr中找不到对应的根文件名信息,则需要新建一个stem_t
    //分配的保存stem_t的空间已满,则需要扩容
    if (data->alloc_stems == num) {
        stem_t *tmp_arr;
        data->alloc_stems = data->alloc_stems * 2 + 16;
        tmp_arr = (stem_t *) realloc(data->stem_arr,
                  sizeof(stem_t) * data->alloc_stems);
        if (!tmp_arr)
            return -1;
        data->stem_arr = tmp_arr;
    }
    //初始化stem_t
    data->stem_arr[num].len = stem_len;
    data->stem_arr[num].buf = (char *) malloc(stem_len + 1);
    if (!data->stem_arr[num].buf)
        return -1;
    memcpy(data->stem_arr[num].buf, buf, stem_len);
    data->stem_arr[num].buf[stem_len] = '\0';
    data->num_stems++;
    buf += stem_len;
    return num;
}

  至此,可以知道selinux_android_file_context_handle()返回的是一个保存了/file_conetxts各种信息及相关处理函数的struct selabel_handle指针,并将其保存在静态全局指针fc_sehandle中,这部分系统中的文件安全上下文有关。同理,selinux_android_prop_context_handle()是对/property_contexts进行信息的处理及保存,与property的设置安全上下文有关,此处不再详述。
  init进程在初始化selinux后,会调用restorecon或restorecon_recursive(递归)恢复文件的安全上下文信息。下面以restorecon为例进行介绍。

/system/core/init/init.cpp

    // If we're in the kernel domain, re-exec init to transition to the init domain now
    // that the SELinux policy has been loaded.
    if (is_first_stage) {
        if (restorecon("/init") == -1) {
            ERROR("restorecon failed: %s\n", strerror(errno));
            security_failure();
        }
        char* path = argv[0];
        char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
        if (execv(path, args) == -1) {
            ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
            security_failure();
        }
    }

    // These directories were necessarily created before initial policy load
    // and therefore need their security context restored to the proper value.
    // This must happen before /dev is populated by ueventd.
    INFO("Running restorecon...\n");
    restorecon("/dev");
    restorecon("/dev/socket");
    restorecon("/dev/__properties__");
    restorecon_recursive("/sys");

/system/core/init/util.cpp

int restorecon(const char* pathname)
{
    return selinux_android_restorecon(pathname, 0);
}

/external/libselinux/src/android.c

int selinux_android_restorecon(const char *file, unsigned int flags)
{
    return selinux_android_restorecon_common(file, NULL, -1, flags);
}

  

/external/libselinux/src/android.c

static int selinux_android_restorecon_common(const char* pathname_orig,
                                             const char *seinfo,
                                             uid_t uid,
                                             unsigned int flags)
{
    bool nochange = (flags & SELINUX_ANDROID_RESTORECON_NOCHANGE) ? true : false;
    bool verbose = (flags & SELINUX_ANDROID_RESTORECON_VERBOSE) ? true : false;
    bool recurse = (flags & SELINUX_ANDROID_RESTORECON_RECURSE) ? true : false;
    bool force = (flags & SELINUX_ANDROID_RESTORECON_FORCE) ? true : false;
    bool datadata = (flags & SELINUX_ANDROID_RESTORECON_DATADATA) ? true : false;
    bool issys;
    bool setrestoreconlast = true;
    struct stat sb;
    FTS *fts;
    FTSENT *ftsent;
    char *pathname;
    char * paths[2] = { NULL , NULL };
    int ftsflags = FTS_NOCHDIR | FTS_XDEV | FTS_PHYSICAL;
    int error, sverrno;
    char xattr_value[FC_DIGEST_SIZE];
    ssize_t size;

    //selinux关闭则返回0
    if (is_selinux_enabled() <= 0)
        return 0;
    //__selinux_once用来保证file_context_init在一个进程内仅会被执行一次
    //file_context_init就是调用之前介绍的file_context_init将记录/file_contexts信息的struct selabel_handle指针保存在fc_sehandle中
    __selinux_once(fc_once, file_context_init);

    if (!fc_sehandle)
        return 0;

    // convert passed-in pathname to canonical pathname
    pathname = realpath(pathname_orig, NULL);
    if (!pathname) {
        sverrno = errno;
        selinux_log(SELINUX_ERROR, "SELinux: Could not get canonical path %s restorecon: %s.\n",
                pathname_orig, strerror(errno));
        errno = sverrno;
        error = -1;
        goto cleanup;
    }
    paths[0] = pathname;
    issys = (!strcmp(pathname, SYS_PATH)
            || !strncmp(pathname, SYS_PREFIX, sizeof(SYS_PREFIX)-1)) ? true : false;
    //非递归恢复安全上下文的情况
    if (!recurse) {
        if (lstat(pathname, &sb) < 0) {
            error = -1;
            goto cleanup;
        }

        error = restorecon_sb(pathname, &sb, nochange, verbose, seinfo, uid);
        goto cleanup;
    }

    /*
     * Ignore restorecon_last on /data/data or /data/user
     * since their labeling is based on seapp_contexts and seinfo
     * assignments rather than file_contexts and is managed by
     * installd rather than init.
     */
     //下面的为处理递归恢复安全上下文的情况
    if (!strncmp(pathname, DATA_DATA_PREFIX, sizeof(DATA_DATA_PREFIX)-1) ||
        !strncmp(pathname, DATA_USER_PREFIX, sizeof(DATA_USER_PREFIX)-1) ||
        !fnmatch(EXPAND_USER_PATH, pathname, FNM_LEADING_DIR|FNM_PATHNAME))
        setrestoreconlast = false;

    /* Also ignore on /sys since it is regenerated on each boot regardless. */
    if (issys)
        setrestoreconlast = false;

    if (setrestoreconlast) {
        size = getxattr(pathname, RESTORECON_LAST, xattr_value, sizeof fc_digest);
        if (!force && size == sizeof fc_digest && memcmp(fc_digest, xattr_value, sizeof fc_digest) == 0) {
            selinux_log(SELINUX_INFO,
                        "SELinux: Skipping restorecon_recursive(%s)\n",
                        pathname);
            error = 0;
            goto cleanup;
        }
    }

    fts = fts_open(paths, ftsflags, NULL);
    if (!fts) {
        error = -1;
        goto cleanup;
    }

    error = 0;
    while ((ftsent = fts_read(fts)) != NULL) {
        switch (ftsent->fts_info) {
        case FTS_DC:
            selinux_log(SELINUX_ERROR,
                        "SELinux:  Directory cycle on %s.\n", ftsent->fts_path);
            errno = ELOOP;
            error = -1;
            goto out;
        case FTS_DP:
            continue;
        case FTS_DNR:
            selinux_log(SELINUX_ERROR,
                        "SELinux:  Could not read %s: %s.\n", ftsent->fts_path, strerror(errno));
            fts_set(fts, ftsent, FTS_SKIP);
            continue;
        case FTS_NS:
            selinux_log(SELINUX_ERROR,
                        "SELinux:  Could not stat %s: %s.\n", ftsent->fts_path, strerror(errno));
            fts_set(fts, ftsent, FTS_SKIP);
            continue;
        case FTS_ERR:
            selinux_log(SELINUX_ERROR,
                        "SELinux:  Error on %s: %s.\n", ftsent->fts_path, strerror(errno));
            fts_set(fts, ftsent, FTS_SKIP);
            continue;
        case FTS_D:
            if (issys && !selabel_partial_match(fc_sehandle, ftsent->fts_path)) {
                fts_set(fts, ftsent, FTS_SKIP);
                continue;
            }

            if (!datadata &&
                (!strcmp(ftsent->fts_path, DATA_DATA_PATH) ||
                 !strncmp(ftsent->fts_path, DATA_USER_PREFIX, sizeof(DATA_USER_PREFIX)-1) ||
                 !fnmatch(EXPAND_USER_PATH, ftsent->fts_path, FNM_LEADING_DIR|FNM_PATHNAME))) {
                // Don't label anything below this directory.
                fts_set(fts, ftsent, FTS_SKIP);
                // but fall through and make sure we label the directory itself
            }
            /* fall through */
        default:
            error |= restorecon_sb(ftsent->fts_path, ftsent->fts_statp, nochange, verbose, seinfo, uid);
            break;
        }
    }

    // Labeling successful. Mark the top level directory as completed.
    if (setrestoreconlast && !nochange && !error)
        setxattr(pathname, RESTORECON_LAST, fc_digest, sizeof fc_digest, 0);

out:
    sverrno = errno;
    (void) fts_close(fts);
    errno = sverrno;
cleanup:
    free(pathname);
    return error;
}

  对于/data/data及/data/user下的app文件,系统不使用/file_contexts里面的安全上下文信息为依据来定义它们的安全上下文,而使用/seapp_contexts作为依据。installd会对这些安全上下文进行管理,而不是由init进程负责。

/external/libselinux/src/android.c

static int restorecon_sb(const char *pathname, const struct stat *sb,
                         bool nochange, bool verbose,
                         const char *seinfo, uid_t uid)
{
    char *secontext = NULL;
    char *oldsecontext = NULL;
    int rc = 0;

    //selabel_lookup在保存安全上下文信息的fc_sehandle中找到对应的安全上下文字符串保存在secontext中
    if (selabel_lookup(fc_sehandle, &secontext, pathname, sb->st_mode) < 0)
        return 0;  /* no match, but not an error */
    //通过kernel保存的安全上下文字符串,保存在oldsecontext中
    if (lgetfilecon(pathname, &oldsecontext) < 0)
        goto err;

    /*
     * For subdirectories of /data/data or /data/user, we ignore selabel_lookup()
     * and use pkgdir_selabel_lookup() instead. Files within those directories
     * have different labeling rules, based off of /seapp_contexts, and
     * installd is responsible for managing these labels instead of init.
     */
    if (!strncmp(pathname, DATA_DATA_PREFIX, sizeof(DATA_DATA_PREFIX)-1) ||
        !strncmp(pathname, DATA_USER_PREFIX, sizeof(DATA_USER_PREFIX)-1) ||
        !fnmatch(EXPAND_USER_PATH, pathname, FNM_LEADING_DIR|FNM_PATHNAME)) {
        //获取app文件的安全上下文信息,保存在secontext中
        if (pkgdir_selabel_lookup(pathname, seinfo, uid, &secontext) < 0)
            goto err;
    }

    //如果安全上下文信息发生了改变,打印log和重写入安全上下文
    if (strcmp(oldsecontext, secontext) != 0) {
        if (verbose)
            selinux_log(SELINUX_INFO,
                        "SELinux:  Relabeling %s from %s to %s.\n", pathname, oldsecontext, secontext);
        if (!nochange) {//将新的安全上下文信息写回kernel
            if (lsetfilecon(pathname, secontext) < 0)
                goto err;
        }
    }

    rc = 0;

out:
    freecon(oldsecontext);
    freecon(secontext);
    return rc;

err:
    selinux_log(SELINUX_ERROR,
                "SELinux: Could not set context for %s:  %s\n",
                pathname, strerror(errno));
    rc = -1;
    goto out;
}

/external/libselinux/src/android.c

static int pkgdir_selabel_lookup(const char *pathname,
                                 const char *seinfo,
                                 uid_t uid,
                                 char **secontextp)
{
    char *pkgname = NULL, *end = NULL;
    struct pkgInfo *pkgInfo = NULL;
    char *secontext = *secontextp;
    context_t ctx = NULL;
    int rc = 0;

    /* Skip directory prefix before package name. */
    if (!strncmp(pathname, DATA_DATA_PREFIX, sizeof(DATA_DATA_PREFIX)-1)) {
        pathname += sizeof(DATA_DATA_PREFIX) - 1;
    } else if (!strncmp(pathname, DATA_USER_PREFIX, sizeof(DATA_USER_PREFIX)-1)) {
        pathname += sizeof(DATA_USER_PREFIX) - 1;
        while (isdigit(*pathname))
            pathname++;
        if (*pathname == '/')
            pathname++;
        else
            return 0;
    } else if (!fnmatch(EXPAND_USER_PATH, pathname, FNM_LEADING_DIR|FNM_PATHNAME)) {
        pathname += sizeof(EXPAND_USER_PATH);
        while (isdigit(*pathname))
            pathname++;
        if (*pathname == '/')
            pathname++;
        else
            return 0;
    } else
        return 0;

    if (!(*pathname))
        return 0;

    pkgname = strdup(pathname);
    if (!pkgname)
        return -1;

    for (end = pkgname; *end && *end != '/'; end++)
        ;
    pathname = end;
    if (*end)
        pathname++;
    *end = '\0';

    if (!seinfo) {
        //根据/data/system/packages.list和app名称获得构建的struct pkgInfo指针
        pkgInfo = package_info_lookup(pkgname);
        if (!pkgInfo) {
            selinux_log(SELINUX_WARNING, "SELinux:  Could not look up information for package %s, cannot restorecon %s.\n",
                        pkgname, pathname);
            free(pkgname);
            return -1;
        }
    }
    //根据入参传入的安全上下文字符串构建一个context_t保存在ctx,context_t将字符串以':'为分隔符分割安全上下文字符串,分别保存在context_t里面的指针数组中
    ctx = context_new(secontext);
    if (!ctx)
        goto err;
    //根据/seapp_contexts和pkgInfo的信息决定新的安全上下文
    rc = seapp_context_lookup(SEAPP_TYPE, pkgInfo ? pkgInfo->uid : uid, 0,
                              pkgInfo ? pkgInfo->seinfo : seinfo, pkgInfo ? pkgInfo->name : pkgname, pathname, ctx);
    if (rc < 0)
        goto err;
    //构建新的安全上下文信息字符串
    secontext = context_str(ctx);
    if (!secontext)
        goto err;
    //新的安全上下文信息字符串不应该与从/file_contexts里面读到的相同
    if (!strcmp(secontext, *secontextp))
        goto out;
    //将新的安全上下文信息字符串写入/sys/fs/selinux/context中,验证新的安全上下文的安全性
    rc = security_check_context(secontext);
    if (rc < 0)
        goto err;

    freecon(*secontextp);
    //保存新的安全上下文信息字符串
    *secontextp = strdup(secontext);
    if (!(*secontextp))
        goto err;

    rc = 0;

out:
    free(pkgname);
    context_free(ctx);
    return rc;
err:
    selinux_log(SELINUX_ERROR, "%s:  Error looking up context for path %s, pkgname %s, seinfo %s, uid %u: %s\n",
                __FUNCTION__, pathname, pkgname, pkgInfo->seinfo, pkgInfo->uid, strerror(errno));
    rc = -1;
    goto out;
}

  以下是seapp_context的查找过程。Android支持多用户和linux的UID,GID,userid为当前用户,appid为当前用户的id号,userid和appid的决定因素可由入参uid决定,uid在packages.list文件可找到(如下面例子中的10003)。这个uid和userid,appid的关系为uid=100000*userid+appid。在这里说明一下,packages.list每一行的对应内容为:包名,uid,app是否在调试模式,app的数据存放路径,app的seinfo信息,app所属的user group,这些内容会被解析保存在seapp_context中。

/data/system/packages.list

com.android.defcontainer 10003 0 /data/data/com.android.defcontainer platform 2001,1023,1015

  查找app文件的安全上下文信息规则如下:在packages.xml文件里定义的每一项属性都要与/seapp_contexts里面的完全符合才算成功匹配,如果packages.xml文件定义了的属性在/seapp_contexts里面没有注明,将不检查该项。

/external/libselinux/src/android.c

static int seapp_context_lookup(enum seapp_kind kind,
                uid_t uid,
                bool isSystemServer,
                const char *seinfo,
                const char *pkgname,
                const char *path,
                context_t ctx)
{
    bool isOwner;
    const char *username = NULL;
    struct seapp_context *cur = NULL;
    int i;
    size_t n;
    uid_t userid;
    uid_t appid;
    //根据文件/seapp_contexts文件构建struct seapp_context指针,保存到struct seapp_context指针数组中。
    __selinux_once(once, seapp_context_init);

    userid = uid / AID_USER;
    isOwner = (userid == 0);
    appid = uid % AID_USER;
    if (appid < AID_APP) {
        for (n = 0; n < android_id_count; n++) {
            if (android_ids[n].aid == appid) {
                username = android_ids[n].name;
                break;
            }
        }
        if (!username)
            goto err;
    } else if (appid < AID_ISOLATED_START) {//普通app走此流程
        username = "_app";
        //appid自减10000
        appid -= AID_APP;
    } else {
        username = "_isolated";
        appid -= AID_ISOLATED_START;
    }

    if (appid >= CAT_MAPPING_MAX_ID || userid >= CAT_MAPPING_MAX_ID)
        goto err;
    //根据pkgInfo携带的入参查找适合的seapp_context
    for (i = 0; i < nspec; i++) {
        cur = seapp_contexts[i];

        //isSystemServer的值要与入参保持一致
        if (cur->isSystemServer != isSystemServer)
            continue;
        //要求app属于owner或ownerset
        if (cur->isOwnerSet && cur->isOwner != isOwner)
            continue;
        //app的user与安全上下文user全名或通配符'*'前面部分要保持一致
        if (cur->user.str) {
            if (cur->user.is_prefix) {
                if (strncasecmp(username, cur->user.str, cur->user.len-1))
                    continue;
            } else {
                if (strcasecmp(username, cur->user.str))
                    continue;
            }
        }

        //app的seinfo与安全上下文的setinfo一致或者app没有设置setinfo
        if (cur->seinfo) {
            if (!seinfo || strcasecmp(seinfo, cur->seinfo))
                continue;
        }
        //app包名与安全上下文指定的包名全名或通配符'*'前面部分保持一致
        if (cur->name.str) {
            if(!pkgname)
                continue;

            if (cur->name.is_prefix) {
                if (strncasecmp(pkgname, cur->name.str, cur->name.len-1))
                    continue;
            } else {
                if (strcasecmp(pkgname, cur->name.str))
                    continue;
            }
        }
        //app路径名与安全上下文指定的包名全名或通配符'*'前面部分保持一致
        if (cur->path.str) {
            if (!path)
                continue;

            if (cur->path.is_prefix) {
                if (strncmp(path, cur->path.str, cur->path.len-1))
                    continue;
            } else {
                if (strcmp(path, cur->path.str))
                    continue;
            }
        }

        if (kind == SEAPP_TYPE && !cur->type)
            continue;
        else if (kind == SEAPP_DOMAIN && !cur->domain)
            continue;

        if (kind == SEAPP_TYPE) {//设置安全上下文的type到kernel,此处走此逻辑
            if (context_type_set(ctx, cur->type))
                goto oom;
        } else if (kind == SEAPP_DOMAIN) {//设置安全上下文的domain到kernel
            if (context_type_set(ctx, cur->domain))
                goto oom;
        }

        if (cur->levelFrom != LEVELFROM_NONE) {
            char level[255];
            switch (cur->levelFrom) {
            case LEVELFROM_APP:
                snprintf(level, sizeof level, "s0:c%u,c%u",
                     appid & 0xff,
                     256 + (appid>>8 & 0xff));
                break;
            case LEVELFROM_USER:
                snprintf(level, sizeof level, "s0:c%u,c%u",
                     512 + (userid & 0xff),
                     768 + (userid>>8 & 0xff));
                break;
            case LEVELFROM_ALL:
                snprintf(level, sizeof level, "s0:c%u,c%u,c%u,c%u",
                     appid & 0xff,
                     256 + (appid>>8 & 0xff),
                     512 + (userid & 0xff),
                     768 + (userid>>8 & 0xff));
                break;
            default:
                goto err;
            }
            //设置安全上下文的level到kernel
            if (context_range_set(ctx, level))
                goto oom;
        } else if (cur->level) {
            if (context_range_set(ctx, cur->level))
                goto oom;
        }

        break;
    }

    if (kind == SEAPP_DOMAIN && i == nspec) {
        /*
         * No match.
         * Fail to prevent staying in the zygote's context.
         */
        selinux_log(SELINUX_ERROR,
                "%s:  No match for app with uid %d, seinfo %s, name %s\n",
                __FUNCTION__, uid, seinfo, pkgname);

        if (security_getenforce() == 1)
            goto err;
    }

    return 0;
err:
    return -1;
oom:
    return -2;
}

  安全上下文信息例如”u:object_r:system_data_file:s0”以冒号为分隔,形式可以写为”user:role:type:sensitivity”。context.h声明了四个设置安全上下文信息组件的函数:context_type_set(设置type或domain字段,context_range_set(设置sensitivity字段),context_role_set(设置role字段)和context_user_set(设置user字段)。通过查找/seapp_contexts文件修改安全上下文的对应字段,组成新的安全上下文字符串,通过lsetfilecon重写入对应文件的安全上下文。

/external/libselinux\include/selinux/context.h

/* Set a context component.  Returns nonzero if unsuccessful */

    extern int context_type_set(context_t, const char *);
    extern int context_range_set(context_t, const char *);
    extern int context_role_set(context_t, const char *);
    extern int context_user_set(context_t, const char *);

  现在已经知道文件的安全上下文是怎么来的。现在来看看进程的安全上下文。
  init进程的可执行程序为/init,它的文件安全上下文为u:object_r:init_exec:s0 init,init_exec是一个file_type,也是一个exec_type,表示是一个可执行的文件,定义在init.te中:

/external/sepolicy/init.te

type init_exec, exec_type, file_type;

  在kernel.te中有一句:

/external/sepolicy/kernel.te

domain_auto_trans(kernel, init_exec, init)

  查看domain_auto_trans的定义。domain_trans进行一些allow操作的设置,type_transition使kernel在执行init_exec类型的可执行文件时,将进程的domain设置为init。使用ps -Z|grep init可以看到,init进程的安全上下文为u:r:init:s0。

/external/sepolicy/te_macros

# domain_auto_trans(olddomain, type, newdomain)
# Automatically transition from olddomain to newdomain
# upon executing a file labeled with type.
#
define(`domain_auto_trans', `
# Allow the necessary permissions.
domain_trans($1,$2,$3)
# Make the transition occur by default.
type_transition $1 $2:process $3;
')

/external/sepolicy/te_macros

# domain_trans(olddomain, type, newdomain)
# Allow a transition from olddomain to newdomain
# upon executing a file labeled with type.
# This only allows the transition; it does not
# cause it to occur automatically - use domain_auto_trans
# if that is what you want.
#
define(`domain_trans', `
# Old domain may exec the file and transition to the new domain.
allow $1 $2:file { getattr open read execute };
allow $1 $3:process transition;
# New domain is entered by executing the file.
allow $3 $2:file { entrypoint open read execute getattr };
# New domain can send SIGCHLD to its caller.
allow $3 $1:process sigchld;
# Enable AT_SECURE, i.e. libc secure mode.
dontaudit $1 $3:process noatsecure;
# XXX dontaudit candidate but requires further study.
allow $1 $3:process { siginh rlimitinh };
')

  默认情况下,父目录下创建的文件安全上下文会继承父目录,除非被显示指定。例如/file_contexts里面使用”/system(/.*)? u:object_r:system_file:s0”指定/system目录下所有文件的file_type为system_file,但是同时有一条更详细的规则:/system/bin/sh – u:object_r:shell_exec:s0,指定/system/bin/sh的file_type为shell_exec而不是system_file。
  对于父进程也是如此,按理说,android所有的进程都是init进程或者是init的后代进程,应该有着和init一样的安全上下文,这是不现实的。但是只有以下这些进程的安全上下文和init进程相同。下面说说android如何设置进程的安全上下文。

u:r:init:s0                    root      1     0     /init
u:r:init:s0                    root      205   1     /system/bin/vold
u:r:init:s0                    root      237   1     /system/vendor/bin/crashlogd
u:r:init:s0                    root      238   1     /vendor/bin/log-watch
u:r:init:s0                    root      575   1     /system/bin/sh
u:r:init:s0                    root      579   575   /system/vendor/bin/logcatext
u:r:init:s0                    media_rw  1006  205   /system/bin/sdcard

  native进程可以在init.*.rc中使用seclabel选项来设置安全上下文。例如:

/system/core/rootdir/init.rc

service adbd /sbin/adbd --root_seclabel=u:r:su:s0
    class core
    socket adbd stream 660 system system
    disabled
    seclabel u:r:adbd:s0
    ...
service console /system/bin/sh
    class core
    console
    disabled
    user shell
    group shell log
    seclabel u:r:shell:s0

  另一方面,也可以使用和kernel转换到init时改变安全上下文一样的方法来设置安全上下文。例如zygote进程。zygote进程的可执行文件为/system/bin/app_process64,文件安全上下文为u:object_r:zygote_exec:s0。可以猜想,init进程fork子进程形成zygote的过程中发生了安全上下文的切换。

/external/sepolicy/zygote.te

type zygote, domain;
type zygote_exec, exec_type, file_type;

init_daemon_domain(zygote)
...

  这里同样出现了domain_auto_trans,表示init进程执行zygote_exec类型的可执行文件使会切换到zygote这个domain,于是zygote进程的安全上下文为u:r:zygote:s0。

/external/sepolicy/te_macros

# init_daemon_domain(domain)
# Set up a transition from init to the daemon domain
# upon executing its binary.
define(`init_daemon_domain', `
domain_auto_trans(init, $1_exec, $1)
tmpfs_domain($1)
')

  应用进程建立在java层,不会有对应的te文件。下面看看应用进程安全上下文的建立。java应用进程的建立需要去读取/system/etc/security/mac_permissions.xml。mac_permissions.xml表明了签名和seinfo的关系。一般地,系统的apk对应的tag为”signer”,使用的是系统的签名,”@PLATFORM”在被编译后会被替换成系统的签名,对应的seinfo为”platform”。第三方apk对应的tag为default,对应的seinfo为”default”。

/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();

/frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java

   public static boolean readInstallPolicy() {
        // Temp structure to hold the rules while we parse the xml file
        List<Policy> policies = new ArrayList<>();

        FileReader policyFile = null;
        XmlPullParser parser = Xml.newPullParser();
        try {
            //MAC_PERMISSIONS指的是/system/etc/security/mac_permissions.xml或/data/security/current/mac_permissions.xml
            policyFile = new FileReader(MAC_PERMISSIONS);
            Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS);

            parser.setInput(policyFile);
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "policy");

            while (parser.next() != XmlPullParser.END_TAG) {
                if (parser.getEventType() != XmlPullParser.START_TAG) {
                    continue;
                }

                switch (parser.getName()) {
                    case "signer":
                        policies.add(readSignerOrThrow(parser));
                        break;
                    case "default":
                        policies.add(readDefaultOrThrow(parser));
                        break;
                    default:
                        skip(parser);
                }
            }
        } catch (IllegalStateException | IllegalArgumentException |
                XmlPullParserException ex) {
            StringBuilder sb = new StringBuilder("Exception @");
            sb.append(parser.getPositionDescription());
            sb.append(" while parsing ");
            sb.append(MAC_PERMISSIONS);
            sb.append(":");
            sb.append(ex);
            Slog.w(TAG, sb.toString());
            return false;
        } catch (IOException ioe) {
            Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS, ioe);
            return false;
        } finally {
            IoUtils.closeQuietly(policyFile);
        }

        // Now sort the policy stanzas
        PolicyComparator policySort = new PolicyComparator();
        Collections.sort(policies, policySort);
        if (policySort.foundDuplicate()) {
            Slog.w(TAG, "ERROR! Duplicate entries found parsing " + MAC_PERMISSIONS);
            return false;
        }

        synchronized (sPolicies) {
            sPolicies = policies;

            if (DEBUG_POLICY_ORDER) {
                for (Policy policy : sPolicies) {
                    Slog.d(TAG, "Policy: " + policy.toString());
                }
            }
        }

        return true;
    }
<?xml version="1.0" encoding="utf-8"?>
<policy>

<!--

    * A signature is a hex encoded X.509 certificate or a tag defined in
      keys.conf and is required for each signer tag.
    * A signer tag may contain a seinfo tag and multiple package stanzas.
    * A default tag is allowed that can contain policy for all apps not signed with a
      previously listed cert. It may not contain any inner package stanzas.
    * Each mac_permissions.xml tag is allowed to contain one seinfo tag. This tag
      represents additional info that each app can use in setting a SELinux security
      context on the eventual process.
    * When a package is installed the following logic is used to determine what seinfo
      value, if any, is assigned.
      - All signatures used to sign the app are checked first.
      - If a signer stanza has inner package stanzas, those stanza will be checked
        to try and match the package name of the app. If the package name matches
        then that seinfo tag is used. If no inner package matches then the outer
        seinfo tag is assigned.
      - The default tag is consulted last if needed.
-->

    <!-- Platform dev key in AOSP -->
    <signer signature="@PLATFORM" >
      <seinfo value="platform" />
    </signer>

    <!-- All other keys -->
    <default>
      <seinfo value="default" />
    </default>

</policy>

  在随后的的扫描apk包的过程中,会为每个应用找到合适的seinfo值:

/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

    private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
            if (mFoundPolicyFile) {
                SELinuxMMAC.assignSeinfoValue(pkg);
            }

/frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java

    public static boolean assignSeinfoValue(PackageParser.Package pkg) {
        synchronized (sPolicies) {
            for (Policy policy : sPolicies) {
                String seinfo = policy.getMatchedSeinfo(pkg);
                if (seinfo != null) {
                    pkg.applicationInfo.seinfo = seinfo;
                    if (DEBUG_POLICY_INSTALL) {
                        Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " +
                               "seinfo=" + seinfo);
                    }
                    return true;
                }
            }
        }

        if (DEBUG_POLICY_INSTALL) {
            Slog.i(TAG, "package (" + pkg.packageName + ") doesn't match any policy; " +
                   "seinfo will remain null");
        }
        return false;
    }

  非default的情况下,会查找mCerts中保存的mac_permissions.xml签名看是否跟入参pkg的签名一致,不一致会返回null。若签名一致,mac_permissions.xml指定了“package”项,则找到对应的seinfo返回。按照这里提供的mac_permissions.xml来看,如果是系统apk,则会可以与 mCerts中的签名对应,返回”platform”的seinfo值;如果是第三方apk,mDefaultStanza为truem,直接返回”default”的seinfo值。最终这个seinfo值会保存在入参的ApplicationInfo成员的seinfo成员中。

/frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java

    public String getMatchedSeinfo(PackageParser.Package pkg) {
        if (!mDefaultStanza) {
            // Check for exact signature matches across all certs.
            Signature[] certs = mCerts.toArray(new Signature[0]);
            if (!Signature.areExactMatch(certs, pkg.mSignatures)) {
                return null;
            }

            // Check for inner package name matches given that the
            // signature checks already passed.
            String seinfoValue = mPkgMap.get(pkg.packageName);
            if (seinfoValue != null) {
                return seinfoValue;
            }
        }

        // Return the global seinfo value (even if it's null).
        return mSeinfo;
    }

  seinfo并不是最终的安全上下文信息。根据启动Activity的Intent,AMS根据解析Intent的结果,在PMS中找到最合适的app package,最终得到一个有着ApplicationInfo成员ProcessRecord对象作为startProcessLocked的入参。

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

       private final void startProcessLocked(ProcessRecord app, String hostingType,
            String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs)
            ...
            Process.ProcessStartResult startResult = Process.start(entryPoint,
                    app.processName, uid, uid, gids, debugFlags, mountExternal,
                    app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                    app.info.dataDir, entryPointArgs); 

/frameworks/base/core/java/android/os/Process.java

    public static final ProcessStartResult start(final String processClass,
                                  final String niceName,
                                  int uid, int gid, int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String abi,
                                  String instructionSet,
                                  String appDataDir,
                                  String[] zygoteArgs) {
        try {
            return startViaZygote(processClass, niceName, uid, gid, gids,
                    debugFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, zygoteArgs);
        } catch (ZygoteStartFailedEx ex) {
            Log.e(LOG_TAG,
                    "Starting VM process through Zygote failed");
            throw new RuntimeException(
                    "Starting VM process through Zygote failed", ex);
        }
    }

  入参会被封装到argsForZygote中,写入到Zygote所在的LocalSocket中,被Zygote Server端读到。

/frameworks/base/core/java/android/os/Process.java

   private static ProcessStartResult startViaZygote(final String processClass,
                                  final String niceName,
                                  final int uid, final int gid,
                                  final int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String abi,
                                  String instructionSet,
                                  String appDataDir,
                                  String[] extraArgs)
                                  throws ZygoteStartFailedEx {
        synchronized(Process.class) {
            ArrayList<String> argsForZygote = new ArrayList<String>();

            // --runtime-args, --setuid=, --setgid=,
            // and --setgroups= must go first
            argsForZygote.add("--runtime-args");
            argsForZygote.add("--setuid=" + uid);
            argsForZygote.add("--setgid=" + gid);
            if ((debugFlags & Zygote.DEBUG_ENABLE_JNI_LOGGING) != 0) {
                argsForZygote.add("--enable-jni-logging");
            }
            if ((debugFlags & Zygote.DEBUG_ENABLE_SAFEMODE) != 0) {
                argsForZygote.add("--enable-safemode");
            }
            if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) {
                argsForZygote.add("--enable-debugger");
            }
            if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
                argsForZygote.add("--enable-checkjni");
            }
            if ((debugFlags & Zygote.DEBUG_ENABLE_JIT) != 0) {
                argsForZygote.add("--enable-jit");
            }
            if ((debugFlags & Zygote.DEBUG_GENERATE_DEBUG_INFO) != 0) {
                argsForZygote.add("--generate-debug-info");
            }
            if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
                argsForZygote.add("--enable-assert");
            }
            if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
                argsForZygote.add("--mount-external-default");
            } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
                argsForZygote.add("--mount-external-read");
            } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
                argsForZygote.add("--mount-external-write");
            }
            argsForZygote.add("--target-sdk-version=" + targetSdkVersion);

            //TODO optionally enable debuger
            //argsForZygote.add("--enable-debugger");

            // --setgroups is a comma-separated list
            if (gids != null && gids.length > 0) {
                StringBuilder sb = new StringBuilder();
                sb.append("--setgroups=");

                int sz = gids.length;
                for (int i = 0; i < sz; i++) {
                    if (i != 0) {
                        sb.append(',');
                    }
                    sb.append(gids[i]);
                }

                argsForZygote.add(sb.toString());
            }

            if (niceName != null) {
                argsForZygote.add("--nice-name=" + niceName);
            }

            if (seInfo != null) {
                argsForZygote.add("--seinfo=" + seInfo);
            }

            if (instructionSet != null) {
                argsForZygote.add("--instruction-set=" + instructionSet);
            }

            if (appDataDir != null) {
                argsForZygote.add("--app-data-dir=" + appDataDir);
            }

            argsForZygote.add(processClass);

            if (extraArgs != null) {
                for (String arg : extraArgs) {
                    argsForZygote.add(arg);
                }
            }

            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
        }
    }

  openZygoteSocketIfNeeded会返回一个ZygoteState对象,里面封装了Zygote的LocalSocket,Zygote LocalSocket的读取对象DataInputStream,Zygote LocalSocket的写入对象BufferedWriter等。输出对象BufferedWriter将argsForZygote写入到LocalSocket中,从而开始让Zygote fork进程,zygoteSendArgsAndGetResult的调用者会通过DataInputStream读取到fork出来的进程的pid,用以判断是否读取成功。

/frameworks/base/core/java/android/os/Process.java

   private static ProcessStartResult zygoteSendArgsAndGetResult(
            ZygoteState zygoteState, ArrayList<String> args)
            throws ZygoteStartFailedEx {
        try {
            /**
             * See com.android.internal.os.ZygoteInit.readArgumentList()
             * Presently the wire format to the zygote process is:
             * a) a count of arguments (argc, in essence)
             * b) a number of newline-separated argument strings equal to count
             *
             * After the zygote process reads these it will write the pid of
             * the child or -1 on failure, followed by boolean to
             * indicate whether a wrapper process was used.
             */
            final BufferedWriter writer = zygoteState.writer;
            final DataInputStream inputStream = zygoteState.inputStream;

            writer.write(Integer.toString(args.size()));
            writer.newLine();

            int sz = args.size();
            for (int i = 0; i < sz; i++) {
                String arg = args.get(i);
                if (arg.indexOf('\n') >= 0) {
                    throw new ZygoteStartFailedEx(
                            "embedded newlines not allowed");
                }
                writer.write(arg);
                writer.newLine();
            }

            writer.flush();

            // Should there be a timeout on this?
            ProcessStartResult result = new ProcessStartResult();
            result.pid = inputStream.readInt();
            if (result.pid < 0) {
                throw new ZygoteStartFailedEx("fork() failed");
            }
            result.usingWrapper = inputStream.readBoolean();
            return result;
        } catch (IOException ex) {
            zygoteState.close();
            throw new ZygoteStartFailedEx(ex);
        }
    }

  Zygote server端在runSelectLoop函数中等待唤醒。当Zygote获知要fork进程的时候,会调用ZygoteConnection的runOnce函数。最后会调到native函数ForkAndSpecializeCommon,中间的代码此处省略。

/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

    private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

        fds.add(sServerSocket.getFileDescriptor());
        peers.add(null);

        while (true) {
            StructPollfd[] pollFds = new StructPollfd[fds.size()];
            for (int i = 0; i < pollFds.length; ++i) {
                pollFds[i] = new StructPollfd();
                pollFds[i].fd = fds.get(i);
                pollFds[i].events = (short) POLLIN;
            }
            try {
                Os.poll(pollFds, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
            for (int i = pollFds.length - 1; i >= 0; --i) {
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }
                if (i == 0) {
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    boolean done = peers.get(i).runOnce();
                    if (done) {
                        peers.remove(i);
                        fds.remove(i);
                    }
                }
            }
        }
    }

/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp

// Utility routine to fork zygote and specialize the child process.
static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids,
                                     jint debug_flags, jobjectArray javaRlimits,
                                     jlong permittedCapabilities, jlong effectiveCapabilities,
                                     jint mount_external,
                                     jstring java_se_info, jstring java_se_name,
                                     bool is_system_server, jintArray fdsToClose,
                                     jstring instructionSet, jstring dataDir) {
                                     ...
    const char* se_info_c_str = NULL;
    ScopedUtfChars* se_info = NULL;
    if (java_se_info != NULL) {
        se_info = new ScopedUtfChars(env, java_se_info);
        se_info_c_str = se_info->c_str();
        if (se_info_c_str == NULL) {
          ALOGE("se_info_c_str == NULL");
          RuntimeAbort(env);
        }
    }
    const char* se_name_c_str = NULL;
    ScopedUtfChars* se_name = NULL;
    if (java_se_name != NULL) {
        se_name = new ScopedUtfChars(env, java_se_name);
        se_name_c_str = se_name->c_str();
        if (se_name_c_str == NULL) {
          ALOGE("se_name_c_str == NULL");
          RuntimeAbort(env);
        }
    }
    rc = selinux_android_setcontext(uid, is_system_server, se_info_c_str, se_name_c_str);
    if (rc == -1) {
      ALOGE("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid,
            is_system_server, se_info_c_str, se_name_c_str);
      RuntimeAbort(env);
    }
    ...

  与设置app文件安全上下文的selinux_android_setfilecon不同的是,设置app进程安全上下文的函数selinux_android_setcontext会根据符合的规则的domain去设置进程的domain,而selinux_android_setfilecon会根据符合的规则的type去设置文件的type,安全上下文其他部分user,role,level的设置差别不大。

/external/libselinux/src/android.c

int selinux_android_setcontext(uid_t uid,
                   bool isSystemServer,
                   const char *seinfo,
                   const char *pkgname)
{
    char *orig_ctx_str = NULL, *ctx_str;
    context_t ctx = NULL;
    int rc = -1;

    if (is_selinux_enabled() <= 0)
        return 0;

    rc = getcon(&ctx_str);//获取当前的安全上下文
    if (rc)
        goto err;

    ctx = context_new(ctx_str);//以当前的安全上下文构成context_t
    orig_ctx_str = ctx_str;
    if (!ctx)
        goto oom;

    //根据UID,isSystemServer,seinfo,pkgname等信息匹配/seapp_contexts的规则,完全符合的项会被用来设置新的context_t,保存在最后一个入参ctx中。seinfo就是之前提到的“platform”或“android”,根据apk是否拥有系统签名而定。
    rc = seapp_context_lookup(SEAPP_DOMAIN, uid, isSystemServer, seinfo, pkgname, NULL, ctx);
    if (rc == -1)
        goto err;
    else if (rc == -2)
        goto oom;

    ctx_str = context_str(ctx);
    if (!ctx_str)
        goto oom;

    rc = security_check_context(ctx_str);
    if (rc < 0)
        goto err;

    //若安全上下文发生了改变,则重新设置安全上下文
    if (strcmp(ctx_str, orig_ctx_str)) {
        rc = setcon(ctx_str);
        if (rc < 0)
            goto err;
    }

    rc = 0;
out:
    freecon(orig_ctx_str);
    context_free(ctx);
    avc_netlink_close();
    return rc;
err:
    if (isSystemServer)
        selinux_log(SELINUX_ERROR,
                "%s:  Error setting context for system server: %s\n",
                __FUNCTION__, strerror(errno));
    else 
        selinux_log(SELINUX_ERROR,
                "%s:  Error setting context for app with uid %d, seinfo %s: %s\n",
                __FUNCTION__, uid, seinfo, strerror(errno));

    rc = -1;
    goto out;
oom:
    selinux_log(SELINUX_ERROR, "%s:  Out of memory\n", __FUNCTION__);
    rc = -1;
    goto out;
}

猜你喜欢

转载自blog.csdn.net/invoker123/article/details/78999058