How Android Permissions Mapped at Kernel's Level
Tech Enthusiast, Activist @ Digitised, Engineer @ Microsoft
Android, as we all know is based, on top of Linux.
Whenever you install an Android app, you are asked to grant certain permissions, ranging from access to gallery, contacts, call logs, camera, location, microphone to body sensors etc. However, have you wondered how these permissions are actually understood and enforced at the system's level?
Every app has it's own set of requested permissions so how are these permissions assigned to the app. In Android, app is nothing but a process.
According to Linux world definition:
"A process is a program in execution, and each process has its own address space, which comprises the memory locations that the process is allowed to access. A process has one or more threads of execution, which are sequences of executable instructions: a single-threaded process has just one thread, whereas a multi-threaded process has more than one thread.
Threads within a process share various resources, in particular, address space. Accordingly, threads within a process can communicate straightforwardly through shared memory. Of interest here is that different processes, by default, do not share memory."
This implies that whenever an app is launched, a process should be forked (from Zygote) with a certain set of permissions and the same shall be abided through while communicating to other apps (processes) through Inter Process Communication (IPC, which is facilitated through Binder in Android, more about it in some other post). Makes sense?
Now having set this intent, let's reverse our point of view and see what happens when the app gets installed first.
Heard of Package Manager?
As per official Android documentation, "It is a class for retrieving various kinds of information related to the application packages that are currently installed on the device."
Whenever you install an app, Package Manager parse the package(APK) file and display confirmation dialog box. When user press OK button, Package Manager call method named "installPackage"
Somewhat like this happens, in Package Manager Service.
- Add a package to the queue for the installation process
- Determine the appropriate location of the package installation
- Determine installation Install / Update new
- A copy of the apk file to a given directory
- Determine the UID of the app
- Request to installd daemon process
- Create the application directory and set permissions
- Extraction of dex code to the cache directory
- To reflect and packages.list / system / data / packages.xml the latest status
- Broadcast to the system along with the name of the effect of the installation is complete package Intent.ACTION_PACKAGE_ADDED: If the new ( Intent.ACTION_PACKAGE_REPLACED): the case of an update
Permissions are assigned to each application (as identified by a unique package name) at install time by the system package manager service. The package manager maintains a central database of installed packages, both preinstalled and user-installed, with information about the install path, version, signing certificate, and assigned permissions of each package, as well as a list of all permissions defined on a device.
This package database is stored in the XML file/data/system/packages.xml, which is updated each time an application is installed, updated, or uninstalled.
Whether a permission is granted or not depends on its protection level which ranges from normal, dangerous, signature, signatureOrSystem.
Permissions are enforced at various layers in Android. Higher-level components such as applications and system services query the package manager to determine which permissions have been assigned to an application and decide whether to grant access.
Lower-level components like native daemons typically do not have access to the package manager and rely on the UID, GID, and supplementary GIDs assigned to a process in order to determine which privileges to grant it. Access to system resources like device files, Unix domain sockets (local sockets), and network sockets is regulated by the kernel based on the owner and access mode of the target resource and the UID and GIDs of the accessing process.
Remember GIDs from Linux world? Here's a summary:
Every file in Unix has the following attributes −
- Owner permissions − The owner's permissions determine what actions the owner of the file can perform on the file.
- Group permissions − The group's permissions determine what actions a user, who is a member of the group that a file belongs to, can perform on the file. [GIDs]
- Other (world) permissions − The permissions for others indicate what action all other users can perform on the file.
When the application is started, the process’s UID and GID are set to the application UID assigned by the installer (the package manager service). If additional permissions have been assigned to the application, they are mapped to GIDs and assigned as supplementary GIDs to the process.Permission to GID mappings for built-in permissions are defined in the /etc/permission/ platform.xml file.
Tadaaaaa, here it is:
There's another interesting file which is android_filesystem_config.h
Android does not have an/etc/group file, so the mapping from group names to GIDs is static and defined in this header file. It defines the owner, access mode, and associated capabilities (for executables) of core Android system directories and files. (We should dig into this file separately someday)
So so so, we know what happens when an app is installed, where are the permissions saved and how are they mapped to specific GIDs (in case of predefined permissions), but how are they enforced? What actually happens when you launch the app i.e a process gets spawned?
Have a look at this code snippet:
As shown here, the child process first sets its supplementary GIDs (corresponding to permissions) using setgroups(), called by setgroupsIntarray() at ➊. Next, it sets resource limits using setrlimit(), called by setrlimitsFromArray() at ➋, then sets the real, effective, and saved user and group IDs using setresgid() ➌ and setresuid() ➍
The child process is able to change its resource limits and all process attributes because it initially executes as root, just like its parent process, zygote.
After the new process attributes are set, the child process executes with the assigned UIDs and GIDs and cannot go back to executing as root because the saved user ID is not 0.
After setting the UIDs and GIDs, the process sets its capabilities using capset(), called from setCapabilities() ➎. Then, it sets its scheduling policy by adding itself to one of the predefined control groups ➏.
Hence, the kernel and system daemons use these process identifiers to decide whether to grant access to a particular system resource or function.
Access to regular files, device nodes, and local sockets is regulated just as it is in any Linux system. One Android-specific addition is requiring processes that want to create network sockets to belong to the group inet (INTERNET is an explicit permission in Android, remember?)
While Binder is the preferred IPC mechanism in Android, lower-level native daemons often use Unix domain sockets (local sockets) for IPC. Because Unix domain sockets are represented as nodes on the filesystem, standard filesystem permission can be used to control access.
- Android Security Internals by Nikolay Elenkov
Subscribe to get your daily round-up of top tech stories!