SEandroid Security Mechanism - File Security Context

Posted by phpnew on Sun, 23 Jun 2019 21:30:45 +0200

2 File Security Context

SEAndroid is a MAC security mechanism based on security policy. This security strategy is implemented in the security context of the subject and the object.

This means that before the implementation of the security policy, the primary and secondary objects in the SEAndroid security mechanism already have security context.

In SEAndroid security mechanism, the main body is generally the process, while the object is generally the document. There are different ways to associate the security context of a file.

This paper mainly analyses the setting process of file security context.

Although there are different kinds of files in android system, once again only the data files of application process, whether system application or third-party application, are discussed.

Their data files are located in the data subdirectory of the data partition, so a mechanism is needed to create the data files in the / data/data directory.

Set different security contexts. When the application is installed, the PackageManagerService will install in / data/data through the daemon process

The corresponding data directory is created in the directory, and the data files created by default in the running process of the application will be located in the corresponding data directory.

Therefore, as long as these data directories are provided with different security contexts, different types of applications can create data files with different security contexts in the process of running.

For example, the file context under the. data/data directory is as follows. These are the file directories of Du apk.

ls  -Z


2.1 Read the mac_permissions.xml file

PackageManagerService is responsible for installing Android applications, and SELinux MMAC is invoked when the service is started (in the constructor).

The readInstallPolicy method reads the mac_permissions.xml file.

mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();

The readInstallPolicy method is as follows.

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 {
            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");
••••

At first glance, this method knows that the xml file is parsed out, then saved in the collection and saved in sPolicies.

private static List<Policy> sPolicies = new ArrayList<>();

PackageManagerService calls the static member function readInstallPolicy of the SELinux MMAC class.

The seinfo string is initialized for the next install of the application.

When the PackageManagerService installs an application, it calls the scanPackageDirtyLI to parse the application's information.

This function also assigns a string to the application being installed, as follows:

if (mFoundPolicyFile) {
     SELinuxMMAC.assignSeinfoValue(pkg);
}

The assignSeinfoValue method is as follows.

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

The resulting seinfo string is stored in the member variable applicationInfo of the Package object used to describe the application installation information.

The member variable seinfo of an ApplicationInfo object described.

2.2 Create a Data Catalog

2.2.1 Java Layer

When the PackageManagerService parses the information of the application to be installed, the createDataDirsLI method is then invoked.

Create a data catalog for the application you are installing. The call flow chart is as follows.


The parameter packageName describes the package name of the application being installed, while the parameters uid and seinfo describe the uid and seinfo assigned to the application being installed.

Installer.java's install method is as follows.

public int install(String uuid, String name, int uid, int gid, String seinfo) {
        StringBuilder builder = new StringBuilder("install");
        builder.append(' ');
        builder.append(escapeNull(uuid));
        builder.append(' ');
        builder.append(name);
        builder.append(' ');
        builder.append(uid);
        builder.append(' ');
        builder.append(gid);
        builder.append(' ');
        builder.append(seinfo != null ? seinfo : "!");
        return mInstaller.execute(builder.toString());
    }

Note that the command sent is install.

InstallerConnection.java's execute method is as follows.

public int execute(String cmd) {
        String res = transact(cmd);
        try {
            return Integer.parseInt(res);
        } catch (NumberFormatException ex) {
            return -1;
        }
    }

The transact method calls the writeCommand and install daemons for socket communication.

     if (!writeCommand(cmd)) {

The writeCommand method is as follows.

private boolean writeCommand(String cmdString) {
        final byte[] cmd = cmdString.getBytes();
        final int len = cmd.length;
        if ((len < 1) || (len > buf.length)) {
            return false;
        }

        buf[0] = (byte) (len & 0xff);
        buf[1] = (byte) ((len >> 8) & 0xff);
        try {
            mOut.write(buf, 0, 2);
            mOut.write(cmd, 0, len);
        } catch (IOException ex) {
            Slog.e(TAG, "write error");
            disconnect();
            return false;
        }
        return true;
    }

The socket corresponding to mOut is in the connect method.

private boolean connect() {
        if (mSocket != null) {
            return true;
        }
        Slog.i(TAG, "connecting...");
        try {
            mSocket = new LocalSocket();
            LocalSocketAddress address = new LocalSocketAddress("installd",
                    LocalSocketAddress.Namespace.RESERVED);
            mSocket.connect(address);
            mIn = mSocket.getInputStream();
            mOut = mSocket.getOutputStream();
        } catch (IOException ex) {
            disconnect();
            return false;
        }
        return true;
    }

The other end of the socket corresponds to the install daemon.

2.2.2 native Layer

The main method of install daemon is also the main method of installd.cpp. In the main method, socket is opened and the commands in socket are read in a loop.

for (;;) {
        alen = sizeof(addr);
        s = accept(lsocket, &addr, &alen);
        if (s < 0) {
            ALOGE("Accept failed: %s\n", strerror(errno));
            continue;
        }
        fcntl(s, F_SETFD, FD_CLOEXEC);

        ALOGI("new connection\n");
        for (;;) {
            unsigned short count;
            if (readx(s, &count, sizeof(count))) {
                ALOGE("failed to read size\n");
                break;
            }
            if ((count < 1) || (count >= BUFFER_MAX)) {
                ALOGE("invalid size %d\n", count);
                break;
            }
            if (readx(s, buf, count)) {
                ALOGE("failed to read command\n");
                break;
            }
            buf[count] = 0;
            if (selinux_enabled && selinux_status_updated() > 0) {
                selinux_android_seapp_context_reload();
            }
            if (execute(s, buf)) break;
        }

After reading the commands in the Java layer, call the execute method to process.

for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) {
        if (!strcmp(cmds[i].name,arg[0])) {
            if (n != cmds[i].numargs) {
                ALOGE("%s requires %d arguments (%d given)\n",
                     cmds[i].name, cmds[i].numargs, n);
            } else {
                ret = cmds[i].func(arg + 1, reply);
            }
            goto done;
        }
    }

There are more than twenty kinds of commands, each of which corresponds to a method of processing.

struct cmdinfo cmds[] = {
    { "ping",                 0, do_ping },
    { "install",              5, do_install },
    { "dexopt",               10, do_dexopt },
    { "markbootcomplete",     1, do_mark_boot_complete },
    { "movedex",              3, do_move_dex },
    { "rmdex",                2, do_rm_dex },
    { "remove",               3, do_remove },
    { "rename",               2, do_rename },
    { "fixuid",               4, do_fixuid },
    { "freecache",            2, do_free_cache },
    { "rmcache",              3, do_rm_cache },
    { "rmcodecache",          3, do_rm_code_cache },
    { "getsize",              8, do_get_size },
    { "rmuserdata",           3, do_rm_user_data },
    { "cpcompleteapp",        6, do_cp_complete_app },
    { "movefiles",            0, do_movefiles },
    { "linklib",              4, do_linklib },
    { "mkuserdata",           5, do_mk_user_data },
    { "mkuserconfig",         1, do_mk_user_config },
    { "rmuser",               2, do_rm_user },
    { "idmap",                3, do_idmap },
    { "restorecondata",       4, do_restorecon_data },
    { "createoatdir",         2, do_create_oat_dir },
    { "rmpackagedir",         1, do_rm_package_dir },
    { "linkfile",             3, do_link_file }
};

The execute method matches the read commands with cmds one by one and calls the corresponding method after the match is successful.

The Install command corresponds to do_install, as follows.

static int do_install(char **arg, char reply[REPLY_MAX] __unused)
{
    return install(parse_null(arg[0]), arg[1], atoi(arg[2]), atoi(arg[3]), arg[4]); /* uuid, pkgname, uid, gid, seinfo */
}

First, get the parameters in the Install command, including the package name, uid and seinfo of the apk being installed, and then call the install method.

if (mkdir(pkgdir, 0751) < 0) {
        ALOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno));
        return -1;
}
•••
if (selinux_android_setfilecon(pkgdir, pkgname, seinfo, uid) < 0) {
        ALOGE("cannot setfilecon dir '%s': %s\n", pkgdir, strerror(errno));
        unlink(pkgdir);
        return -errno;
    }

The install method first creates the installation directory corresponding to the apk and sets the permission to 0715.

The selinux_android_setfilecon method is then called to set the security context for the directory.

2.3 Setting Security Context

Setting the security context of the file is also done in the libselinux.so library.

The selinux_android_setfilecon method of android.c is as follows.

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

	if (is_selinux_enabled() <= 0) //Is SELinux enabled
		return 0;

	rc = getfilecon(pkgdir, &ctx_str);// Get the original existing security context
                     //When a file or directory is created, the default setting is the security context of the parent directory.
	if (rc < 0)
		goto err;
 // Create a new security context ctx so that you can get information about SELinux users, roles, and security levels in the original security context.
	ctx = context_new(ctx_str);
	orig_ctx_str = ctx_str;
	if (!ctx)
		goto oom;
               //According to the incoming parameter seinfo, the corresponding Type is found in the seapp_contexts file.
//And set it to the Type of the new security context ctx.
	rc = seapp_context_lookup(SEAPP_TYPE, uid, 0, seinfo, pkgname, NULL, ctx);
	if (rc == -1)
		goto err;
	else if (rc == -2)
		goto oom;
              //String to get the newly created security context
	ctx_str = context_str(ctx);
	if (!ctx_str)
		goto oom;
               //Verify the correctness of the newly created security context
	rc = security_check_context(ctx_str);
	if (rc < 0)
		goto err;

	if (strcmp(ctx_str, orig_ctx_str)) {
                         // Setting the newly created security context
		rc = setfilecon(pkgdir, ctx_str);
		if (rc < 0)
			goto err;
	}
            •••
}

So far, from Java layer to native, and finally to libselinux.so library, the process of setting file security context in SEAndroid security mechanism has been analyzed.

Topics: xml Java socket SELinux