RFC-0155: Optional capability routes

RFC-0155: Optional capability routes
StatusAccepted
Areas
  • Component Framework
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 protocol fuchsia.location.sensor.WlanBaseStationWatcher, but this protocol is not present on some products.
  • The component /core/bt-a2dp must use the fuchsia.power.battery.BatteryManager capability if it is available, but it is not available on all products.
  • The /core/omaha-client-service component must use the fuchsia.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