Security basics of the Android operating system. Native user space, part 2

    Introduction


    Today I will continue to consider security at a level slightly above the core. In the second part, we will look at where system.img, userdata.img, and cache.img come from, as well as how security is provided in Native user space.
    Everyone who is interested, welcome!



    List of Articles

    Here are links to my articles from this topic:
    1. Security basics of the Android operating system. Core level
    2. Security basics of the Android operating system. Native user space, part 1
    3. Security basics of the Android operating system. Native user space, part 2
    4. Security basics of the Android operating system. Security at the Application Framework level. Binder IPC



    What is meant by native user space

    Native user space refers to all user space components that run outside the Dalvik Virtual Machine and which are not part of the Linux kernel. Native user space are executable files compiled for a specific architecture. These include executable files that are launched from the init script automatically or in the event of an event, toolbox utilities, as well as some executable files that the user can run from under the shell.



    Start


    As I said in the first part, Android is based on the Linux kernel. As with all Linux systems, Android’s security is based on access control. Those. each resource (for example, a file) contains meta-information about who created this file - owner (owner) - and to which main group (owner group) belongs to owner (owner). Each process starts on behalf of some user. Each user has a core group. In addition, he may be a member of other groups. Thus, if you attach information to each resource (in rwxrwxrwx format) about who can read / write / execute the resource (for example, a file), then you can control access to this file. For example, you can assign permissions to a file: what can the owner (owner) of this file do with this file; what can users do which are part of the owner group; what everyone else can do.Here you can read more about this.

    But Android has some differences. Firstly, initially Android is an operating system for phones, which, as you know, belong to very personal things and which we do not like to give into the wrong hands. That is, it was conceived as an operating system with only one user. Therefore, it was decided to use various Linux users for security (for each application - a separate user, as I already said in the first article). Secondly, in Android, some user (users) and their UID (identifiers) were hard-coded into the system, which causes a lot of complaints from people related to security (although, to be honest, I do not really understand why this approach is criticized). We have already seen these users in the file.system / core / include / private / android_filesystem_config.h For example, root has the identifier 0 , and system - 1000 .

    As I already noted, the process starts on behalf of the same user (UID) as the process that starts this new process, i.e. UID (calling_process) == UID (called_process). The first process that runs on Android is init- Run as root (UID == 0). Thus, in theory, all processes should also be started on behalf of the same users. So it probably would have been. But, firstly, processes running on behalf of a privileged user (as well as those with certain capabilities) can change their UID to a less privileged one. And secondly, in Android, when launching daemons in the init.rc script, you can also specify with which user privileges and which groups to start this process.

    ...
    service console /system/bin/sh
        class core
        console
        disabled
        user shell
        group log
    ...
    service servicemanager /system/bin/servicemanager
        class core
        user system
        group system
        critical
        onrestart restart zygote
        onrestart restart media
        onrestart restart surfaceflinger
        onrestart restart drm
    ...
    service media /system/bin/mediaserver
        class main
        user media
        group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc
        ioprio rt 4
    ...  
    

    All processes that will be launched through these daemons will no longer have root privileges.



    System, data and cache


    I have announced this topic so many times that one would think that it is very complex and confusing. In fact, this is not so. System.img , userdata.img and cache.img are the results of compiling the Android operating system. That is, as a result of the assembly of the system, these three files are obtained, which we write to our device.

    But the most important thing is not this. Due to the fact that in the Android system the user name and UID of system users are hard-coded, already at the compilation stage we can determine the access rights of various system users to various directories in these images. These permissions are specified in the file system / core / include / private / android_filesystem_config.hwhich we have already examined in the first article. Access rights are defined separately for directories (android_dirs []) and separately for files (android_files []) as follows:

    ...
    struct fs_path_config {
        unsigned mode;
        unsigned uid;
        unsigned gid;
        uint64_t capabilities;
        const char *prefix;
    };
    /* Rules for directories.
    ** These rules are applied based on "first match", so they
    ** should start with the most specific path and work their
    ** way up to the root.
    */
    static const struct fs_path_config android_dirs[] = {
        { 00770, AID_SYSTEM, AID_CACHE,  0, "cache" },
        { 00771, AID_SYSTEM, AID_SYSTEM, 0, "data/app" },
        { 00771, AID_SYSTEM, AID_SYSTEM, 0, "data/app-private" },
        { 00771, AID_SYSTEM, AID_SYSTEM, 0, "data/dalvik-cache" },
        { 00771, AID_SYSTEM, AID_SYSTEM, 0, "data/data" },
        { 00771, AID_SHELL,  AID_SHELL,  0, "data/local/tmp" },
        { 00771, AID_SHELL,  AID_SHELL,  0, "data/local" },
        { 01771, AID_SYSTEM, AID_MISC,   0, "data/misc" },
        { 00770, AID_DHCP,   AID_DHCP,   0, "data/misc/dhcp" },
        { 00775, AID_MEDIA_RW, AID_MEDIA_RW, 0, "data/media" },
        { 00775, AID_MEDIA_RW, AID_MEDIA_RW, 0, "data/media/Music" },
        { 00771, AID_SYSTEM, AID_SYSTEM, 0, "data" },
        { 00750, AID_ROOT,   AID_SHELL,  0, "sbin" },
        { 00755, AID_ROOT,   AID_SHELL,  0, "system/bin" },
        { 00755, AID_ROOT,   AID_SHELL,  0, "system/vendor" },
        { 00755, AID_ROOT,   AID_SHELL,  0, "system/xbin" },
        { 00755, AID_ROOT,   AID_ROOT,   0, "system/etc/ppp" },
        { 00777, AID_ROOT,   AID_ROOT,   0, "sdcard" },
        { 00755, AID_ROOT,   AID_ROOT,   0, 0 },
    };
    /* Rules for files.
    ** These rules are applied based on "first match", so they
    ** should start with the most specific path and work their
    ** way up to the root. Prefixes ending in * denotes wildcard
    ** and will allow partial matches.
    */
    static const struct fs_path_config android_files[] = {
        { 00440, AID_ROOT,      AID_SHELL,     0, "system/etc/init.goldfish.rc" },
        { 00550, AID_ROOT,      AID_SHELL,     0, "system/etc/init.goldfish.sh" },
        { 00440, AID_ROOT,      AID_SHELL,     0, "system/etc/init.trout.rc" },
        { 00550, AID_ROOT,      AID_SHELL,     0, "system/etc/init.ril" },
        { 00550, AID_ROOT,      AID_SHELL,     0, "system/etc/init.testmenu" },
        { 00550, AID_DHCP,      AID_SHELL,     0, "system/etc/dhcpcd/dhcpcd-run-hooks" },
        { 00440, AID_BLUETOOTH, AID_BLUETOOTH, 0, "system/etc/dbus.conf" },
        { 00444, AID_RADIO,     AID_AUDIO,     0, "system/etc/AudioPara4.csv" },
        { 00555, AID_ROOT,      AID_ROOT,      0, "system/etc/ppp/*" },
        { 00555, AID_ROOT,      AID_ROOT,      0, "system/etc/rc.*" },
        { 00644, AID_SYSTEM,    AID_SYSTEM,    0, "data/app/*" },
        { 00644, AID_MEDIA_RW,  AID_MEDIA_RW,  0, "data/media/*" },
        { 00644, AID_SYSTEM,    AID_SYSTEM,    0, "data/app-private/*" },
        { 00644, AID_APP,       AID_APP,       0, "data/data/*" },
        { 00755, AID_ROOT,      AID_ROOT,      0, "system/bin/ping" },
        /* the following file is INTENTIONALLY set-gid and not set-uid.
         * Do not change. */
        { 02750, AID_ROOT,      AID_INET,      0, "system/bin/netcfg" },
        /* the following five files are INTENTIONALLY set-uid, but they
         * are NOT included on user builds. */
        { 06755, AID_ROOT,      AID_ROOT,      0, "system/xbin/su" },
        { 06755, AID_ROOT,      AID_ROOT,      0, "system/xbin/librank" },
        { 06755, AID_ROOT,      AID_ROOT,      0, "system/xbin/procrank" },
        { 06755, AID_ROOT,      AID_ROOT,      0, "system/xbin/procmem" },
        { 06755, AID_ROOT,      AID_ROOT,      0, "system/xbin/tcpdump" },
        { 04770, AID_ROOT,      AID_RADIO,     0, "system/bin/pppd-ril" },
        /* the following file has enhanced capabilities and IS included in user builds. */
        { 00750, AID_ROOT,      AID_SHELL,     (1 << CAP_SETUID) | (1 << CAP_SETGID), "system/bin/run-as" },
        { 00755, AID_ROOT,      AID_SHELL,     0, "system/bin/*" },
        { 00755, AID_ROOT,      AID_ROOT,      0, "system/lib/valgrind/*" },
        { 00755, AID_ROOT,      AID_SHELL,     0, "system/xbin/*" },
        { 00755, AID_ROOT,      AID_SHELL,     0, "system/vendor/bin/*" },
        { 00750, AID_ROOT,      AID_SHELL,     0, "sbin/*" },
        { 00755, AID_ROOT,      AID_ROOT,      0, "bin/*" },
        { 00750, AID_ROOT,      AID_SHELL,     0, "init*" },
        { 00750, AID_ROOT,      AID_SHELL,     0, "charger*" },
        { 00750, AID_ROOT,      AID_SHELL,     0, "sbin/fs_mgr" },
        { 00640, AID_ROOT,      AID_SHELL,     0, "fstab.*" },
        { 00644, AID_ROOT,      AID_ROOT,      0, 0 },
    };
    ...
    

    And the static inline void fs_config function (const char * path, int dir, unsigned * uid, unsigned * gid, unsigned * mode, uint64_t * capabilities) , which is defined later in this file, is responsible for setting owner, owner group, capabilities and access rights . This function is called during image assembly .

    In general, everything here should be more or less clear, with the exception of setting permissions flags(setuid and setgid) for some files (for example, for “system / xbin / su”, access rights are defined as 06755, where the first 6 means that the user ID setting flag (4) and the group ID setting flag (2) are set). Setting these flags means that the user can increase the rights of the launched process to the level of owner (owner) of the file or owner group (owner group). In the case of Android, each application is a user with its own UID and GID. Thus, by default, if you run some native executable from your application, it executes with the same UID and GID as the application that called it. Setting these permissions flags allows you to perform native executable with owner rights. In our case, the owner is AID_ROOT (root). This happens with the following system / extras / su / su.c :

    int main(int argc, char **argv)
    {
        ...
        int uid, gid, myuid;
        ...
        if(setgid(gid) || setuid(uid)) {
            ...
        }
    ...
    }
    

    Those. the setuid and setgid functions are called . In this case, if these functions were completed successfully, the process starts to work on behalf of the owner and owner group of this file. In our example, this process receives superuser rights, i.e. he can do whatever he wants :) Such anarchy is not always justified, so Linux introduced the concept of capabilities . So the run-as application does not need all the superuser rights, it only needs to be able to change its identifier in order to run applications on behalf of various users. By the way, capabilities seem to have appeared recently - in Android 2.3.x I have not seen them.



    Security


    In the case of privileged programs (such as su), it is necessary to limit the range of applications that these programs can call. Otherwise, any application can gain superuser rights. Therefore, very often UID verification is built into such programs:

    ...
    #include 
    ...
    int main(int argc, char **argv)
    {
        struct passwd *pw;
        int uid, gid, myuid;
        /* Until we have something better, only root and the shell can use su. */
        myuid = getuid();
        if (myuid != AID_ROOT && myuid != AID_SHELL) {
            fprintf(stderr,"su: uid %d not allowed to su\n", myuid);
            return 1;
        }
        ...
    }
    

    Those. the program first checks on whose behalf the calling process is started using the getuid () function . And then compares these values ​​with values ​​that are hard-coded into the system. In this case, only processes running on behalf of users “system” and “root” have the right to use su .



    Conclusion


    In this article, we have finished analyzing how security is provided at the Native user space level. In the following articles I plan to analyze how permission works, but due to the large current load, I don’t know when I will start writing them. As always, I will be very happy with additions and corrections.

    PS We were invited to prepare a report on DevConf @ mobi Do you think the audience will be interested in the report on the security of the Android operating system at a conference that is more focused on application developers?


    References


    1. "Embedded Android" by Karim Yaghmour
    2. Android Security Underpinnings by Marko Gargenta
    3. Linux Users and Groups
    4. Image configuration
    5. Suid
    6. Overview of capabilities

    Also popular now: