What an App Carries Into the Operating System

PHANTOM’s central input is not a mysterious binary blob; it is the ordinary metadata that tells Android or iOS what an application is. This chapter examines Android’s manifest and Apple’s Info.plist as system integration contracts. The goal is to see which decisions these files feed, who consumes the resulting state, and why a value that looks like app configuration can become an operating-system input.

Android’s manifest is more than a build file

Every Android project has an AndroidManifest.xml. Google’s documentation describes it as a source of essential information for build tools, the Android operating system, and Google Play. That three-part audience is already a useful warning. The manifest is not only a note to the compiler, and it is not only a file the application reads after launch. It is part of the platform’s description of the installed package.

The most familiar declarations identify the application’s components. An activity represents a screen or interaction entry point. A service represents work that can run without a visible screen. A broadcast receiver can respond to system or application events, and a content provider exposes structured data through a platform interface. An application that defines one of these component types must declare the corresponding component in the manifest for the system to discover and start it. The declaration therefore connects a class supplied by the developer to a decision owned by the operating system: whether and how that component can be activated.

The manifest also describes permissions. An application declares permissions it needs to access protected data or system features, such as contacts, messaging, a camera, or network access. It can also place permissions around its own components so that other applications must satisfy a condition before using them. The declaration does not grant every permission by itself; on newer Android versions, some permissions still require a user decision at runtime. But the manifest creates the request and gives the platform the information it needs to evaluate the application’s relationship to protected services.

Device compatibility is another system-facing responsibility. A manifest can state required hardware or software features and the minimum platform level the application supports. Distribution can use those declarations to decide which devices are eligible for installation. The operating system can use them to understand how the package fits the device it is entering. Component discovery, permission handling, and compatibility filtering are different jobs, but they share a property that matters for PHANTOM: they are performed by platform machinery outside the application process.

The format’s structure also affects how much data a consumer may eventually handle. Android allows many elements to repeat, and it uses attributes for values associated with those elements. A manifest may refer to a resource rather than placing every displayed string directly in the XML. These are normal design features. Repetition is useful when an application declares several components or capabilities. Resource references allow the same application to adapt labels and assets to different languages, screens, or configurations.

Normal format features can nevertheless create different resource-accounting questions downstream. A parser may inspect one attribute at a time, while a later package query assembles many attributes and repeated entries into one response. A build-time check may see a short resource name while a later system stage resolves the referenced resource. The point is not that repetition or references are inherently unsafe. It is that syntax validity and downstream cost are different properties. A declaration can be valid in the format while still requiring every later consumer to impose its own safe bounds.

The Android platform therefore sees more than a set of strings. It sees an object model of installed applications: components to discover, permissions to represent, features to compare, and values to return when other services ask about packages. The application author supplies the declarations, but system services own the decisions and the persistent representation created from them. That difference in ownership is where an apparently local value begins to acquire system-wide consequences.

A component declaration illustrates the handoff. The developer describes a class and, where relevant, the kinds of intents that can reach it. Android then uses that declaration when it decides which component matches an incoming request and whether the system can start it. The application does not control the whole lookup process. The platform must maintain a searchable view of components across many installed packages, and other services may ask for that view. The declaration has become an entry in a system-managed index.

A permission declaration illustrates a different handoff. The application states what protected capability it needs, but the operating system owns the decision about whether access is allowed. Some decisions are made at installation or package-management time; others are exposed to the user at runtime. In either case, the declaration must be carried into a representation that security-sensitive platform code can consult. A value that appears in a developer-facing file can therefore participate in policy decisions without the application being the immediate caller.

Device compatibility adds a distribution-facing handoff. A declared feature can affect whether a package is offered to a device at all, while the same package still has to be understood by the operating system if it is installed through another permitted route. This is a reminder that “the store checked it” and “the OS parsed it safely” are not interchangeable statements. Different actors consume the metadata for different purposes, and each actor has its own responsibility to bound the work it performs.

The same layered reading applies to resources. An attribute may contain a direct value, or it may name a value stored elsewhere in the package. A developer sees one logical setting. A platform component may see the setting after resolving resources, translating it for a device configuration, copying it into an object, and returning it through an API. None of those steps requires the value to be executable. They do require the platform to account for the value’s size and multiplicity at the point where it is materialized. PHANTOM focuses on what happens when those accounting rules are missing or applied only at an earlier, narrower stage.

Apple’s bundle metadata reaches system surfaces too

Apple defines Info.plist as a resource containing key-value pairs that identify and configure a bundle. A bundle can contain an executable of several kinds, and the property list tells the system how to interpret the associated bundle. Some keys describe the bundle itself; others configure application behavior, execution, protected resources, data movement, or services. Apple also notes that the file has a fixed name and a platform-dependent location, which reinforces that it is part of the bundle contract rather than arbitrary application data.

The useful comparison with Android is not that the two formats have identical keys. It is that both give the operating system declarative information before ordinary application behavior begins. A bundle identifier, display name, version value, capability declaration, or service setting can be needed by a system component that indexes the bundle, presents it to a user, or decides how to connect it to a platform feature. The application may later read some of these values through the bundle API, but the system also has reasons to read them independently.

This creates two views of the same metadata. The application can ask its bundle for a value while it is running, but a system daemon can read that value while installing the bundle, presenting an icon, managing an extension, or rebuilding an index. The value’s meaning is therefore not limited to the code path that explicitly requests it. A display name can become part of a system interface; an extension identity can become part of registration for a shared surface; a version or capability value can help a service decide how to handle the bundle. The platform must preserve a coherent view of these values even when the application is not active.

The surface grows when an iOS application contains App Extensions. An extension is a separate bundle that lets an application provide a feature to a system surface such as a widget, share sheet, or keyboard. It has its own metadata. This means an application package is not necessarily one metadata object with one consumer. It can contain a parent bundle and additional extension bundles, each of which may be interpreted by a service associated with that system surface.

That relationship explains why PHANTOM studies extension metadata separately. A value in a parent application and a value in a Widget Extension may look similar as a key-value pair, but the system may read them in different contexts and at different times. The eventual severity depends on the consumer. The same broad class of unbounded value can produce an on-demand service problem in one context and a boot-time problem in another. The file format alone does not determine the outcome; the processing boundary does.

Android and iOS also differ in how much of their implementation is visible to a researcher, but the system-model question is shared. In both cases, package metadata describes an entity that the operating system must register, index, expose, or connect to other services. The metadata is declarative in form, yet operational in effect. It changes what the platform believes is installed and what the platform will do when another component asks about it.

flowchart LR subgraph Android A["Android manifest"] --> A1["Components, permissions,\ncompatibility, resources"] end subgraph iOS B["Info.plist and extension plists"] --> B1["Bundle identity, execution,\nservices, extensions"] end A1 --> C["Privileged system consumers"] B1 --> C C --> D["Indexed or persistent\npackage and bundle state"] D --> E["Queries, UI surfaces,\nand lifecycle services"]

The ownership boundary is the security boundary

The most important distinction is not between XML and property-list syntax. It is between the party that supplies a value and the components that must safely consume it. The package author controls declarations within the format’s allowed space. The operating system owns the package database, the component registry, the permission model, and the services that answer questions about installed software.

That creates a chain of responsibility. A build tool may check whether a declaration is well formed. An installer may check whether the package can be accepted. A package manager may parse the declaration into an internal object. A launcher or system service may later request that object and serialize it for another process. These stages can have different limits because they perform different work. The first stage may validate grammar; the later stage may need to account for aggregate size, cross-process transmission, memory allocation, or persistence encoding.

The interface between those stages is where ownership can become ambiguous. The parser owns the act of turning bytes into a value. The package manager owns the stored description of the application. A querying service owns the response it is preparing for another process. If each assumes that a previous stage has already checked the relevant limit, no stage necessarily checks the limit that matters at the final interface. A field can be acceptable as XML or a property-list value, acceptable in an internal object, and still unsafe when combined with other fields or placed into a bounded message.

PHANTOM’s finding is that the chain can lose the fact that the original value was external input. Once metadata has been placed into a trusted-looking package object or database, later code may assume that its producer already enforced reasonable bounds. A system service then inherits a value without inheriting a guarantee about its cost. That is the difference between trusting a description and trusting the resource demands of interpreting the description.

This also explains why a signed package does not settle the question. Signing can protect the relationship between the package and its signer. It does not turn every declaration into a system-approved limit, and it does not tell each consumer how much memory, serialized space, or retry activity the value may cause. A signed package can still be an external input to the platform’s parsers. The safety obligation remains with the component that crosses the boundary and materializes the value.

The next question is therefore temporal. When does Android turn manifest declarations into persistent package state? When do iOS installation services and extension consumers read property lists? Which consumers run during ordinary use, and which ones run during boot? Once those transitions are visible, the later failures stop looking like a file suddenly “crashing the phone.” They become failures in a chain of platform-owned state changes.

References