The custom-device interface

The custom-device interface permits access to a device of a specific class and model without requiring the creation of an interface for that device alone. It’s intended to be used with Ubuntu Core and its scope and specification are defined as part of the gadget snap for the deployed Ubuntu Core image.

To permit access, the application snaps defines a custom-device plug, which is associated with a corresponding and identically-named custom-device slot in the gadget snap.

Under specific and appropriate circumstances, it is possible to define the slot directly from the consuming application itself, together with the plug, which is an acceptable approach for applications that will be widely distributed but support very specific hardware.

Using the custom-device requires Store approval and permissions, both to allow the presence of the slot, and to set up elements such as the slot self-connecting exactly to the plug on the app. The more specific the tagging information provided by the slot, the easier it will be to allow for this.

The slot-side of the interface is used to derive which udev rules are provided to the plug-side of the connection:

slots:
  dual-sd:
    interface: custom-device
    custom-device: my-dual-sd-device
    devices: 
      - /dev/DualSD

To prevent connection to arbitrary custom-device slots, the plug and slot must share the same custom-device attributes, including the name of the plug or slot:

plugs:
  dual-sd:
    interface: custom-device
    custom-device: my-dual-sd-device
apps:
  app:
    plugs: [dual-sd]

When the slot and plug are connected, a udev rule is automatically generated and tagged for the plug side for each device path in the devices and read-devices attributes, such as:

KERNEL=="DualSD",

Note that here, the default udev KERNEL rule is the full device path following the leading /dev/. Depending on the device drivers, some devices expect the KERNEL rule to be only the basename of the device path, even if that device is in a subdirectory of /dev/. For this reason, when udev-tagging attributes are not given for a device which is in a subdirectory of /dev/, default KERNEL rules are generated for both the basename and for the full device path following the leading /dev/.

If the udev-tagging attribute is used, this default udev rule is replaced with more specific rules, as described below. When a device name in the kernel attribute does not match the device path listed in the devices section, such as if the KERNEL udev attribute is different than the /dev/... path, an optional for-device attribute can be added to establish a correspondence, such that both udev matching rules and the AppArmor profile is generated correctly.

Requires snapd version 2.55+, while udev-tagging.for-device attribute support requires snapd version 2.66+.

Interface documentation:

See Interface management and Supported interfaces for further details on how interfaces are used.


Developer details

Auto-connect: no
Super-privileged: yes

Attributes:

  • custom-device (optional) (plug, slot): A label for the custom device.
    The label must be identical across the plug and slot connections.
    If omitted, the label defaults to either local slot name or local plug name.
  • devices (slot): paths to device nodes.
    Example: devices: [/dev/input/event[0-9], /dev/input/mice]
  • files (optional) (slot):
    • read (slot): list of files and/or directories for read-only access by the device.
      Example: read: [ /dev/input/by-id/* ]
    • write (slot): list of files and/or directories for read/write access by the device.
      Example: write: [ /etc/file-write, /etc/dir-write ]
  • read-devices (optional) (slot): paths to device nodes for read-only access. Example: read-devices: [ /dev/js* ]
  • udev-tagging (optional): used to tailor the generated udev rules. Can be one of the following:
    • kernel: (mandatory): maps to the string used as the udev KERNEL== filter rule.
    • subsystem: corresponds to the SUBSYSTEM== filters in a udev rule.
    • environment: a map of expected environment variables for the udev rule to match with ENV{...}=="..."
    • attributes: a map of attributes used with ATTR{...}=="..."
    • for-device: indicates which device the udev-tagging snippet refers to

Code examples

A truncated example showing how the subsystem and attributes can be used:

   udev-tagging:
     - kernel: hiddev0
       subsystem: usb
       attributes:
         idVendor: "0x03f0" # HP
     - kernel: hiddev1
       subsystem: usb
       attributes:
         idVendor: "0x03fc" # ECS

An example slot declaration showing the how the kernel environment settings can be used with a custom joystick interface:

slots:
  hwdev:
    interface: custom-device
    custom-device: custom-joystick
    devices:
      - /dev/input/js{[0-9],[12][0-9],3[01]}
      - /dev/input/event[0-9]*
    files:
      read:
        - /run/udev/data/c13:{6[5-9],[7-9][0-9],[1-9][0-9][0-9]*}
        - /run/udev/data/c13:{[0-9],[12][0-9],3[01]}
        - /sys/devices/**/input[0-9]*/capabilities/*
    udev-tagging:
      - kernel: input/event[0-9]*
        subsystem: input
        environment:
          ID_INPUT_JOYSTICK: "1"

The above example will generate the following udev tags:

spec.TagDevice(`KERNEL=="js{[0-9],[12][0-9],3[01]}"`)
spec.TagDevice(`KERNEL=="input/js{[0-9],[12][0-9],3[01]}"`) 
spec.TagDevice(`SUBSYSTEM=="input", KERNEL=="input/event[0-9]*", ENV{ID_INPUT_JOYSTICK}=="1"`) 

The following example shows the udev-tagging syntax:

   slots:
      v4l:
        interface: custom-device
        devices:
          - /dev/video[0-9]
        files:
          read:
            - /sys/bus/usb/devices
            - /sys/class/video4linux
            - /sys/kernel/debug/sleep_time
          write:
            - /proc/sys/vm/stat_interval
        udev-tagging:
          - kernel: video[0-9]
            subsystem: v4l
            environment:
              var1: foo
              var2: bar
            attributes:
              attr1: one
              attr2: two

An additional example, showing how to match udev rules with a device which appears at a different path under /dev/..:

   slots:
      msr:
        interface: custom-device
        devices:
          - /dev/cpu/[0-9]*/msr
        udev-tagging:
          - kernel: msr[0-9]*
            subsystem: msr
            for-device: /dev/cpu/[0-9]*/msr

The above example generates a udev rule to match KERNEL=="msr[0-9]*", SUBSYSTEM=="msr" and an AppArmor rule allowing read/write access through /dev/cpu/[0-9]*/msr.

The test code can be found in the snapd repository: https://github.com/snapcore/snapd/blob/master/interfaces/builtin/custom_device_test.go

The source code for the interface is in the snapd repository: https://github.com/snapcore/snapd/blob/master/interfaces/builtin/custom_device.go

1 Like

Hi,

Thanks for the documentation on custom-device plug.

Had query on udev-tagging option.

In custom-joystick example, js and event nodes defined as below

 devices:
      - /dev/input/js{[0-9],[12][0-9],3[01]}
      - /dev/input/event[0-9]*
  udev-tagging:
      - kernel: js[0-9]*
      - kernel: event[0-9]*
        subsystem: input

I too tried similar slot declaration. But getting snap warning and custom-device plug not working.

That is because of below check in custom_device.go (https://github.com/snapcore/snapd/blob/master/interfaces/builtin/custom_device.go#L160) .

// furthermore, the kernel name must match the name of one of
// the given devices
if !strutil.ListContains(devices, "/dev/"+deviceName) {
		err = fmt.Errorf(`%q does not match a specified device`, deviceName)
}

To resolve error, need to define “kernel” field as below.

  udev-tagging:
      - kernel: input/js[0-9]*
      - kernel: input/event[0-9]*
        subsystem: input

With this, udev tags generating like

spec.TagDevice(`KERNEL=="input/js[0-9]*"`) 
spec.TagDevice(`SUBSYSTEM="input", KERNEL=="input/event[0-9]*")

But with this udev-tag, custom-device plug not working for me as expected and my snap fails to access dev node with EPERM error.

Hi @degville ,

Taking a look at the snapd source, it looks like the files attribute (and its read and write sub-attributes) are slot-side, not plug-side. Additionally it looks like this is a Super-privilged interface.

2 Likes

You’re right - thanks so much for letting me know, and for checking in the actual source code.

1 Like

@degville apologies for the second follow-up, taking me a bit to fully work my way through understanding the interface.

Understand the following is truncated, but maybe worth noting that the list items in the snippet are not actually legal because they do not include a kernel key?

In the following rule, the 3rd list item under udev-tagging isn’t legal. Per the source, all udev kernel rules must map to a rule under devices (sans the /dev/ prefix). Given that afaik udev doesn’t support {} expansion syntax like AppArmor does, and it looks like snapd just does a string match comparison, I’m also skeptical that - kernel: js[0-9]* would be considered legal by snapd (even if you or I could tell that they are essentially equivalent in this case). In fact, I don’t even think that the event[0-9]* rule is legal, because /dev/input/event[0-9]* != /dev/event[0-9]*` but that’s potentially more of something that needs fixing in snapd.

Additionally, in the generated tags at the bottom, I’m almost certain that all the single = should actually be double ==, which is has a fairly different meaning.

Hi @degville

I’ve noticed that read-devices attribute is referenced in the documentation, but it is not explicitly listed in the attributes section.

Thanks!

That’s a great spot, thanks so much for letting me know. I’ve now added it.

1 Like

Does this statement imply that this snapd interface is not available in classic Ubuntu systems?

Fantastic to hear from you. I will check with the team, but my understanding is that there’s nothing to stop the custom-device working with classic, but it’s not the intended design for the interface (which is super-privileged, and requires extra extra scrutiny).

1 Like

Hi @degville :wave:

Did you ever follow up on the question above regarding use of the interface on classic systems ?

Cheers, Just