Before you start#
Review namespaces and cgroup v2, security policy, portals, and desktop session investigation. Flatpak 1.16.0 is pinned to commit 1440f4faa67ebf69c7559f31d2cab59e6ec6fe2b; bubblewrap v0.10.0 is pinned separately to dc63ec667e6546f34e0b6c088cdf8ae7c7dea0f3. These are source baselines, not a tested combination.
Learning goals#
Distinguish a deployed application, runtime, manifest and sandbox process; trace permission context into a mount operation; explain portal grants and host authority; and analyze failure without creating a sandbox on the host.
Source walkthrough#
Read the text version
- Manifest: Static permissions and runtime → Sandbox filesystem: Mounts and namespace view (constrains mounts)
- Manifest: Static permissions and runtime → External service: Bus and portal policy (sets static access)
- Portal grant: User-authorized access to a resource → External service: Bus and portal policy (provides scoped grant)
Flatpak packages applications and runtimes using named references and deployed content. A reference identifies a branch, architecture and application or runtime; an immutable content commit identifies particular content. An application deployment and its runtime are separate objects. A build manifest describes how software is built and what permissions are requested, whereas a running process has the effective permissions resulting from deployment, overrides, policy and actual launch. A manifest is not itself an already running sandbox, and a matching application ID is not proof of trustworthy behavior.
This exact excerpt comes from Flatpak 1.16.0 common/flatpak-run.c, in the environment setup path. The file notice grants LGPL-2.1-or-later; surrounding device and temporary-directory logic is omitted:
exports = flatpak_context_get_exports_full (context,
app_id_dir, previous_app_id_dirs,
TRUE, TRUE,
&xdg_dirs_conf, &home_access);
The function derives an exports object from the permission context, application data locations and output configuration. The returned object is then queried for visibility and used in sandbox preparation. This is an explicit handoff from policy description to filesystem exposure; the call alone does not prove that a particular host path is visible, that a mount succeeds or that the application has opened a file. The exact context and subsequent mount setup must be inspected.
The second excerpt is from bubblewrap 0.10.0 bubblewrap.c, the PRIV_SEP_OP_BIND_MOUNT branch. Its SPDX identifier is LGPL-2.0-or-later. The surrounding switch and error handling are omitted:
bind_result = bind_mount (proc_fd, arg1, arg2, BIND_RECURSIVE | flags, &failing_path);
if (bind_result != BIND_MOUNT_SUCCESS)
die_with_bind_result (bind_result, errno, failing_path,
"Can't bind mount %s on %s", arg1, arg2);
The operation requests a recursive bind using a source, destination and flags. It checks the result and terminates with an error that includes the failing path when the bind fails. In the surrounding source, privilege-separation requests are passed through a socket when that mode is used, and the privileged setup side validates operations. A requested mount is not a successful mount, and a failed setup must not be described as a running protected application. Other paths, including unprivileged user-namespace setup, have different conditions and errors.
Just before that branch, the same case carries a comment on why binds are recursive. In bubblewrap.c, bubblewrap 0.10.0, LGPL-2.0-or-later per its header (the bind call that follows is omitted):
case PRIV_SEP_OP_BIND_MOUNT:
/* We always bind directories recursively, otherwise this would let us
access files that are otherwise covered on the host */
The comment ties the recursive flag to the host's view: a non-recursive bind would leave out submounts beneath the source, so files the host covers with another mount could become visible through the bind. The excerpt does not show which operations use this flag or whether every bind in the file follows the same rule.
Content, identity, and trust#
An OSTree-style content store can identify immutable filesystem content by commit, while a ref points to a named version or branch. A deployment selects a particular commit and arranges application and runtime files for use. Updates can move a ref to a different commit; recording the ref alone may therefore be insufficient to reproduce a particular filesystem. A runtime supplies libraries and services expected by applications, but it shares the host kernel. An application can depend on a runtime ABI and extension versions; the host's devices and kernel remain additional compatibility boundaries.
Signatures and remote configuration can authenticate content origin and integrity when correctly configured and verified. They do not establish that an application is harmless, that every dependency is secure or that requested permissions are narrow. Trust depends on the configured keys, source, verification policy and selected commit. A manifest's build commands can run code during a build, and its requested permissions can expose host data during execution. Review those as separate stages and authorities. No image, runtime or application is downloaded or installed for this lesson.
A Flatpak application ID names an application in the relevant deployment and integration context. It is used in paths, bus policy and portal decisions, but it is not a Linux UID or an unforgeable global identity in every context. An application can have a private data directory and still receive broad access through static permissions, a portal grant, an inherited descriptor or a service. A sandbox's effective authority is the union of actual accessible resources, not a label. A process with access to a host file can cause changes permitted by the host's UID, ACL and security policy.
From permissions to a process#
A context collects permissions such as filesystem access, network sharing, sockets, devices and D-Bus policy. Flatpak prepares a filesystem view, runtime and application mounts, environment and service access for the child. Bubblewrap is one tool for constructing a namespace-based process environment; it does not interpret every application policy by itself. A particular deployment may use unprivileged user namespaces or a configured setuid fallback depending on build and host policy. Neither is guaranteed on every machine, and failure should be reported rather than bypassed by weakening host settings.
A mount namespace gives a process a particular view of mount points. Bind mounts expose selected source paths at destinations; read-only flags limit writes through that mount but do not revoke another descriptor or another writable path to the same object. A private /proc, /dev and temporary directory can have deliberately limited contents. A network namespace can alter network visibility, while sharing the host network gives a different authority. Seccomp filters selected system calls and arguments under their actual policy; it is not a substitute for filesystem, device or D-Bus mediation. The host kernel, credentials and active LSM continue to enforce their own rules.
Bubblewrap's source constructs setup operations and can communicate with a privileged helper over a socket in its privilege-separation mode. The setup side checks and applies operations within the intended namespace. If a source path is missing or a bind fails, the quoted branch reports the failure. File descriptors used for setup, privilege separation and child communication have lifetimes distinct from the final application process. The exact cleanup and parent-observation path depends on which setup branch failed; a termination status or EOF must be handled as failure rather than mistaken for an application that started and exited normally.
The sequence below is a teaching model of the privilege-separation path described above. It is not a sandbox that was launched.
- Flatpak launcherbubblewrap
Pass context and setup arguments
- bubblewrapPrivileged setup side
Send bind or mount operation over socket
- Privileged setup sidebubblewrap
Return. Result or failing path
- bubblewrapFlatpak launcher
Return. Report failure; close setup channels
- bubblewrapApplication child
Exec only after setup succeeds
Time runs downward. Dashed arrows show returns.
Read the text version
- Flatpak launcher → bubblewrap: Pass context and setup arguments
- bubblewrap → Privileged setup side: Send bind or mount operation over socket
- Privileged setup side → bubblewrap (return): Result or failing path
- bubblewrap → Flatpak launcher (return): Report failure; close setup channels
- bubblewrap → Application child: Exec only after setup succeeds
The kernel can deny a user namespace or mount operation because of configuration, permission, resource exhaustion or policy. A request for a namespace is not proof that the child entered it. A process with UID zero inside a user namespace is mapped to a host identity and does not become root in the initial user namespace. Capabilities apply relative to the governing namespace and operation. An inherited file descriptor to a host file can convey authority even when its pathname is not visible in the child's filesystem; the descriptor's access mode and operation-specific kernel checks still constrain its use, so a mount diagram alone is incomplete.
Static permissions and portal grants#
A static filesystem permission can expose a chosen directory, the home directory or a broader host view, with access mode and implementation details depending on the configuration. Network access, an X11 socket or broad D-Bus access can expose additional authority beyond files. X11 clients can have powerful interactions with the display server depending on the environment; a sandbox with broad display and host access should not be described as having no access to other resources. The effective policy must be examined in context, including user overrides and inherited descriptors.
A portal is an external service that can mediate a user-facing request and return a scoped result. A document portal can make a selected file available through an appropriate mechanism; an fd can also convey access to an already opened object. A user-approved grant is dynamic authority that can differ from the static filesystem view. Its scope, persistence, revocation and actual backend behavior depend on the portal and operation. A dialog or portal request is not automatically approval, and a canceled or denied request does not provide the requested file.
The diagram below separates three routes by which a path or descriptor can reach the child. Each route still passes host checks at use, so the diagram is not a complete policy for any installed application.
Read the text version
- Static permission: A manifest or override names a filesystem path → Mount view path: A path that exists inside the namespace (bind-mounted into the view)
- Portal grant: A user-approved, scoped reference to one resource → Open file description: The kernel object an fd refers to (returns an fd or document reference)
- Inherited descriptor: An already-open object passed into the child → Open file description: The kernel object an fd refers to (same object; no path needed)
- Static permission: A manifest or override names a filesystem path → Host checks: DAC, ACL and LSM rules applied on each access (checked on access)
- Portal grant: A user-approved, scoped reference to one resource → Host checks: DAC, ACL and LSM rules applied on each access (checked on access)
- Inherited descriptor: An already-open object passed into the child → Host checks: DAC, ACL and LSM rules applied on each access (checked on each use)
D-Bus proxies and policy restrict which names and operations can be reached under a particular setup. They do not make the message payload harmless or ensure that a permitted service performs only a single expected action. A service that accepts a powerful request can provide substantial indirect authority. Diagnose the application ID, bus, destination, interface, method and returned error; do not assume a socket path alone expresses the whole policy. This lesson does not contact a portal or an existing desktop bus.
Worked manifest and mount graph#
The following is synthetic data, not a copied deployment. Imagine an application org.example.Editor requests its runtime and a writable application-data directory, but no broad home access, no host network sharing and no unrestricted session-bus access. Its prepared view contains a read-only runtime at /usr, application files at /app, a private writable data location and the selected device and temporary filesystem entries. A host file /home/user/private.txt is not included by this stated model. An attempted path open should therefore fail if no alternative mount, inherited fd, portal grant or other service provides access and host policy does not expose it through some different route.
Now suppose the user selects a single authored document through an authorized portal in a hypothetical isolated session. The portal supplies an appropriately scoped document reference or descriptor according to its implementation. The application can then access that selected resource under the granted mode, even though the broad home directory remains absent. This does not authorize access to every sibling file, and it does not prove the application cannot send the contents elsewhere if another allowed channel exists. The portal result, application process credentials and kernel checks are all relevant.
Change the model: a static override grants broad home read/write access. The original inference that the host document is unavailable is no longer justified. A signature on the application does not remove that access. Change it again: the selected bind source is missing during setup. The cited bubblewrap branch reports a failed bind instead of constructing the intended view. The launcher should observe failure, close setup channels and avoid presenting a successful app launch. These examples are not actual Flatpak runs and do not assert the exact effective policy of a particular desktop.
Failure, races, and lifecycle#
The source path from permission context to exports and bind operations spans multiple processes and data structures. A path can change between policy evaluation and a later open, especially when symlinks or renames are involved. Robust implementations use appropriate fd-relative and namespace-safe operations, but a source excerpt from one branch does not prove that every path race is solved. When reviewing an access decision, identify the object actually opened and its descriptor, not just the string previously checked.
A child can fail before exec because namespace creation, uid mapping, mount setup, seccomp installation or executable lookup fails. The parent needs to distinguish a setup error from an application exit and reap the child. If a helper process holds a socket endpoint open after the intended child has exited, EOF may be delayed; each inherited descriptor must be closed by the correct owner. Temporary files and mounts inside a private namespace disappear only as their owning references and processes are released; persistent files outside that namespace can remain. The exact failure-pipe and helper behavior needs the specific implementation path, not a generic assertion that all cleanup is automatic.
A reasonable incident record contains the requested permissions, effective overrides, application and runtime commits, sandbox tool version, user-namespace availability and precise failed setup operation. A missing file error can arise from a deliberately absent mount, a misspelled path, a denied portal grant or host DAC/LSM policy. A successful sandbox launch does not prove that an application's later access attempts will succeed, and an access denial does not prove the sandbox tool is broken. Never change host policy simply to make an example pass.
Practice#
This private JSON model parses an authored permission list without invoking Flatpak, bubblewrap, a portal or host namespace operations:
python3 - <<'PY'
import json
manifest = json.loads('{"id":"org.example.Editor","filesystems":["app-data"],"network":false}')
for target in ('app-data', 'home'):
print(target, 'listed in synthetic manifest:', target in manifest['filesystems'])
PY
The output marks app-data true and home false. This is string membership, not a Flatpak permission evaluator; effective overrides, portal grants and inherited fds are intentionally absent. As a counterexample, adding home changes the model and invalidates a claim that home is absent. If Python is unavailable, inspect the supplied JSON by hand.
For a source-only alternative, draw the supplied /usr, /app, private data and absent-home mount graph. Add the single-document portal grant and identify who owns each descriptor or decision. Then change the bind source to missing and explain the quoted error branch and parent cleanup obligations. Deliver the graph without installing an image, opening a portal or creating a sandbox.
Knowledge check#
- Why is a signed application not necessarily safe or narrowly permissioned?
- What does the Flatpak exports call establish, and what does it not establish?
- Why can an inherited fd grant access even when a path is absent from the mount view?
- What happens in the cited bubblewrap bind branch on failure?
- Why is root inside a user namespace not automatically host root?
Answers#
- A signature can support origin and integrity verification under a configured trust policy; behavior and granted authority still require review.
- It derives an exports object from context and related inputs. It does not by itself show the final effective mount graph or a successful file open.
- An fd is an existing reference to a kernel object. Removing a pathname from a namespace does not automatically revoke that reference or the access already granted through it.
- The branch reports the bind failure and terminates that setup path. The parent still has to observe the error and clean up its child and descriptors.
- UID and capabilities are scoped and mapped through namespaces. Authority over the initial host namespace and host objects requires separate permission.
Checkpoint#
Submit the synthetic mount graph, portal-grant row, bind-failure timeline and descriptor ownership table. Pass when static access, dynamic grants and inherited fds are separately accounted for and a failed setup is not called a successful sandbox. Revisit the source and retry if a signature is treated as behavioral proof. This is not certification.