RFC-0155: Optional capability routes | |
---|---|
Status | Accepted |
Areas |
|
Description | A new field is added to the offer and use sections of component manifests to denote when a given capability may not exist. |
Issues | |
Gerrit change | |
Authors | |
Reviewers | |
Date submitted (year-month-day) | 2022-02-07 |
Date reviewed (year-month-day) | 2022-04-14 |
Summary
A new field is added to the offer and use sections of component manifests to denote when a given capability may not exist.
Motivation
Core realm shards
When core realm shards are used, this introduces scenarios where a capability route's source or target do not exist. For example,
- The component
/core/wlancfg
uses the protocolfuchsia.location.sensor.WlanBaseStationWatcher
, but this protocol is not present on some products. - The component
/core/bt-a2dp
must use thefuchsia.power.battery.BatteryManager
capability if it is available, but it is not available on all products. - The
/core/omaha-client-service
component must use thefuchsia.update.config.OptOut
protocol if it is available, but it is not available on all products. - The
CommsAgent
package must use video if it's available, but there is no video available on some products. - Trace provider is a component which provides the
fuchsia.tracing.provider.Registry
capability. This is useful for development purposes, but this component should not be included on user builds.
The above scenarios are component configurations we want to support going forward, but there are currently a few places in the Component Framework that introduce rough edges related to this:
- Offering to or from a component that does not exist within the manifest will cause manifest validation to fail.
- Offering a capability from a component that does not expose it will cause scrutiny validation to fail.
- Failing a capability route (e.g. attempting to use a capability that was not offered) causes component manager to (correctly) emit a warn-level log, which can look concerning and thus be misleading when investigating issues on products where the capability is intentionally omitted.
Route validation through collections
Scrutiny is a component route validation tool that ensures that all used capabilities within a given component tree have valid capability routes. Aside from the system correctness assertions it provides, this is a useful tool for development as it provides for shorter development cycles by moving route validation from run-time to buildtime.
Today an allowlist is maintained that disables scrutiny checking of specific capability routes. This allows builds to succeed when a component uses an unavailable capability, but it unfortunately also allows builds to succeed when the capability route is misconfigured on builds where the capability is available.
Additionally once https://fxbug.dev/42174612 is resolved, scrutiny will also be validating capability routes that originate in the session realms. Oftentimes the session components depend on capabilities originating from outside of the session, which will add friction if one of these components wishes to be added to the scrutiny allowlist as these components may be developed outside of fuchsia.git.
Stakeholders
Who has a stake in whether this RFC is accepted? (This section is optional but encouraged.)
Facilitator: Hunter Freyer (hjfreyer@google.com)
The person appointed by FEC to shepherd this RFC through the RFC process.
Reviewers:
- Mark Dittmer (markdittmer@google.com) - Security
- Gary Bressler (geb@google.com) - Component framework
- Marc Khouri (mnck@google.com) - WLAN
- Ani Ramakrishnan (aniramakri@google.com) - Bluetooth
- Yaar Schnitman (yaar@google.com) - Products
Socialization:
After receiving requirements from https://fxbug.dev/42168255 and by soliciting feedback from some specific individuals, an early form of this RFC was shared among stakeholders.
Design
A new field named availability
will be added to use declarations. The default
value for this field will be required
, which means that the component is
expecting the capability to be provided and will likely malfunction if it is not
available. This RFC doesn't propose any changes to the behavior of required
capabilities - they'll behave the way all capabilities do today
{
use: [
{
protocol: "fuchsia.logger.LogSink",
availability: "required",
},
{
directory: "config-data",
path: "/config",
rights: [ "r*" ],
// The `availability` field defaults to `required` when omitted.
},
],
}
The availability
field on use declarations may also be set to optional
, to
reflect when a component can function properly when the capability is not
available (likely with modified behavior).
{
use: [
{
// Emergency location is not present in all products.
protocol: "fuchsia.location.sensor.WlanBaseStationWatcher",
availability: "optional",
},
],
}
A new source named void
will be added to the possible list of sources for
offer declarations.
{
offer: [
{
protocol: "fuchsia.update.config.OptOut",
from: "void",
},
],
}
A capability whose use declaration is optional can either be offered from an existing component, or from 'void'. A capability whose use declaration is required may not be offered from void, as this would likely cause the component to malfunction.
Intermediate offers (from parent to a child) may also set the availability
field to one of the following values:
required
(default): the child must receive access to this capability, regardless of if the child can handle its absence or not (it may not be offered from void in an ancestor).optional
: the child must be able to handle the absence of this capability (the use declaration at the end of the offer chain must be optional).same_as_target
: the offer will have the same optionality as the target. If the target requires the capability, then this offer stipulates that the target must receive the capability. If the target has an optional use for the capability, then this offer stipulates that the target may or may not receive this capability.
{
offer: [
{
protocol: "fuchsia.power.battery.BatterManager",
from: "parent",
to: "bt-a2dp",
// Emit a build-time error if this protocol is not correctly routed
// to this child (including offers from void).
availability: "required",
},
{
protocol: "fuchsia.location.sensor.WlanBaseStationWatcher",
from: "parent",
to: "wlancfg",
// Emit a build-time error if this child is unable to handle the
// absence of this protocol.
availability: "required",
},
],
}
If a component has an optional
use and its parent offers the capability as
required
, then a build-time error will be emitted if the route for the
capability is invalid or ends in an "offer from void
". If a component has a
required
use and its parent offers the capability as optional
, then a
build-time error will be emitted. This field may be unset, in which case it is
ignored.
Core realm assembly
As described in RFC-0089, the core realm is assembled from "core shards", which are CML snippets that get merged in with the core realm at build time. The exact set of core shards is set by the product definition.
It is generally advised to include the offers targeting a child in the same core shard as the child declaration. This helps to ensure that if a child is included in the build, then the capabilities it needs to function are also made available to it.
Including a child's offers in the same shard as the child gets complicated if
the offers wish to have a source that is also optionally included in the build.
If an offer with a target of child a
has a source of child b
, but b
is
also in a shard, then a
's shard may only be included on builds where b
's
shard is included as well, or a manifest validation error is emitted at
build-time due to the offer from a non-existent child.
To allow a child's offers to live in the same core shard as the child's
declaration, regardless of the source, a new field will be introduced to offer
declarations named source_availability
. This field will default to the value
present
, but may be set to unknown
.
When source_availability
is set to unknown
the offer declaration will pass
manifest validation if it references a source that is not in the manifest.
During manifest compilation the missing source of the offer declaration will be
replaced with the void
source, allowing any use declarations whose routes end
at this offer declaration to be able to pass route validation by setting their
availability to optional
, to reflect that the capability is not present on all
products.
{
offer: [ {
protocol: "fuchsia.examples.Echo",
from: "#child-that-might-not-be-declared",
to: "#echo-user",
source_availability: "unknown",
} ],
}
This allows the platform configuration maintainer to rely on the set of core
shards as the single source of truth for what capabilities and components are
present in the core realm. Omitting a core shard not only removes a subsystem
from the core realm, but also accurately updates any offers with a source of
that subsystem to offer from void
, so that any components that have optional
usage on that subsystem will gracefully not receive access to those subsystems
when they are intentionally excluded from the system (while any required usages
do still cause errors in this case).
Examples
A component in the core realm with an optional capability
For this example, let's look at how the manifest and core realm shard for
wlancfg
would change based on the proposed design.
The offer for the fuchsia.location.sensor.WlanBaseStationWatcher
protocol
would move from emergency.core_shard.cml
to wlancfg.core_shard.cml
, and the
source_availability
field is set to unknown
(because the emergency core
shard, and with it the emergency component, may or may not be included in the
same platform configurations as wlancfg
).
// src/connectivity/location/emergency/meta/emergency.core_shard.cml
{
offer: [
// This is removed
{
protocol: "fuchsia.location.sensor.WlanBaseStationWatcher",
from: "#emergency",
to: "#wlancfg",
},
],
}
// src/connectivity/wlan/wlancfg/meta/wlancfg.core_shard.cml
{
offer: [
// This is added
{
protocol: "fuchsia.location.sensor.WlanBaseStationWatcher",
from: "#emergency",
to: "#wlancfg",
source_availability: "unknown",
},
],
}
The move of the offer declaration to the core shard for wlancfg
is necessary
for the source_availability
field to work correctly, and is also in-line with
core shard best practices.
In addition to the core shard changes, the use declaration for this protocol in
wlancfg
would be updated to be optional
.
// src/connectivity/wlan/wlancfg/meta/wlancfg.cml
{
use: [
{
protocol: "fuchsia.location.sensor.WlanBaseStationWatcher",
// This line is added
availability: "optional",
},
],
}
Now when the emergency.core_shard.cml
file is not included in the build there
will be no build-time error due to wlancfg
not being able to access the
fuchsia.location.sensor.WlanBaseStationWatcher
protocol. This means the
protocol may be removed from scrutiny's allowlist, and any configuration errors
that cause the protocol to become unavailable to wlancfg
on platform
configurations where it is available will cause build-time errors.
A component in the session with an optional capability
For this example, let's look at how the manifests and core realm shard involved
in getting the fuchsia.power.battery.BatteryManager
protocol from
/core/battery_manager
to
/core/session_manager/session:session/workstation_session/login_shell/ermine_shell
would change based on the proposed design.
The offer for the protocol in the workstation.core_shard.cml
file would be
updated to set source_availability
to unknown
:
// src/session/bin/session_manager/meta/workstation.core_shard.cml
{
offer: [
protocol: "fuchsia.power.battery.BatteryManager",
from: "#battery_manager",
to: "#session-manager",
// This line is added
source_availability: "unknown",
],
}
The offers between core
and ermine_shell
would not need to be altered, and
the ermine_shell
manifest would be updated to set the availability
field to
optional
for the protocol:
// src/experiences/session_shells/ermine/shell/meta/ermine.cml
{
use: [
{
protocol: "fuchsia.power.battery.BatteryManager",
availability: "optional",
},
],
}
With these changes the workstation.core.shard.cml
file may be included in
platform configurations without battery_manager.core_shard.cml
, without
causing build-time scrutiny errors or adding this capability to the allowlist.
This means the battery manager component may be safely excluded from platform
configurations intended to run the workstation session on hardware without a
battery.
A session owner wants to ensure that an optional capability is always
available
Building on the preceding example, consider a fictional session named
workstation-on-laptops
which is identical to the workstation
session except
it has additional laptop-specific abilities. If the owner of this session wishes
to use the same ermine
component that is present on the workstation
session,
but wanted to ensure that it always has access to the
fuchsia.power.battery.BatteryManager
protocol (enforced by a build-time
error), then the session owner may offer this protocol as required
to
ermine
.
// src/experiences/session_shells/ermine/session/meta/workstation_on_laptops_session.cml
{
offer: [
protocol: "fuchsia.power.battery.BatteryManager",
from: "parent",
// login_shell offers the capability to ermine
to: "#login_shell",
availability: "required",
],
}
In this example if the workstation-on-laptops
session is compared against a
platform configuration that offers the fuchsia.power.battery.BatteryManager
protocol from void
(i.e. the battery_manager.core_shard.cml
is not
included), then a build-time error will be emitted, despite ermine.cml
having
an optional use for the protocol.
A session owner wants to ensure that a component can handle the absence of
an optional capability
Again building on the "ermine optionally uses BatteryManager
" example, if the
owner of the workstation session wishes to ensure that ermine
will always be
able to handle the absence of the fuchsia.power.battery.BatteryManager
protocol regardless of the protocol's current availability, they may offer this
protocol as optional
to ermine
.
// src/experiences/session_shells/ermine/session/meta/workstation_session.cml
{
offer: [
protocol: "fuchsia.power.battery.BatteryManager",
from: "parent",
// login_shell offers the capability to ermine
to: "#login_shell",
availability: "optional",
],
}
This optional offer has no impact on the build or run-time behavior with the
given arrangement, as ermine also has an optional use of the capability. If
ermine is ever altered to require this capability however, then a build-time
error will be emitted by scrutiny, regardless of if the capability route is
valid or ends in an "offer from void
".
Implementation
To implement these changes, the fuchsia.component.decl.Component
FIDL table
and CML json schema will have to be updated to include the new fields, along
with CMC, cm_fidl_validator
, and Scrutiny being updated to correctly handle
the optional
semantics described in the design section.
Performance
The proposed changes will not have any significant impact on build times or sizes, as the cost of carrying an additional enum in these manifests is very small. Additionally the proposed run-time behavior change in component manager will not have any significant impact on performance, as the information needed to implement the new namespace assembly logic is all in memory by the time this work begins.
Ergonomics
This change should significantly improve the ergonomics for any component author who currently needs to interact with an allowlist to suppress scrutiny validation errors, as the context around expected validation errors will be improved such that scrutiny won't emit these expected errors at all.
Backwards Compatibility
This change is purely additive to a FIDL table and a JSON format, and thus has no backwards compatibility concerns.
Security considerations
This change will make it possible for component authors to disable Scrutiny errors for unrouted capabilities without receiving approval from the Security team, as an allowlist will no longer be used to control Scrutiny error suppression for unrouted capabilities.
This is deemed to be acceptable because the review processes for creating or altering the component routes themselves is unchanged by this proposal.
Privacy considerations
The proposed changes do not impact how user data is collected, processed, or stored, and thus do not have any privacy concerns.
Testing
There are already extensive tests covering the component manifest pipeline. These tests will be altered to include coverage for this new feature.
Documentation
The component manifest documentation will be updated to describe the new field and its impacts on validation.
Drawbacks, alternatives, and unknowns
Prior art and references
TODO