[{"data":1,"prerenderedAt":33},["ShallowReactive",2],{"note-pins-on-first-use-en":3},{"slug":4,"order":5,"title":6,"summary":7,"measured":8,"topic":9,"toc":10,"html":32},"pins-on-first-use",30,"\"Pins on first use: how plugin-hostd trusts a plugin binary\"","plugin-hostd records the hash of a plugin's files and of its parameter layout the first time the plugin is added, checks them on every later add, and refuses loudly when they change, so that a standalone host gets pins without a controller sending them.","plugin-hostd 0.3.0 and later.","host",[11,14,17,20,23,26,29],{"id":12,"title":13},"why","Why",{"id":15,"title":16},"settings","Settings",{"id":18,"title":19},"one-line-format","One line format",{"id":21,"title":22},"first-use","First use",{"id":24,"title":25},"a-change","A change",{"id":27,"title":28},"the-pins-file-of-plugin-qualify","The pins file of plugin-qualify",{"id":30,"title":31},"a-format-neutral-host","A format-neutral host","\u003Ch2 id=\"why\">Why\u003C\u002Fh2>\n\u003Cp>While \u003Ccode>require_pins\u003C\u002Fcode> is on, an unpinned plugin is refused (-508), so a standalone user's first \u003Ccode>add\u003C\u002Fcode> would fail unless a\ncontroller sent \u003Ccode>pin_set\u003C\u002Fcode> first. A host that does not pin by itself would have to turn \u003Ccode>require_pins\u003C\u002Fcode> off, and then no\npin is checked at all. So the pin is taken on first use and kept, and any change after that is loud.\u003C\u002Fp>\n\u003Cp>An external plugin carries no pins of its own, and its qualification record is evidence, not a pin. A pins file is shipped\nonly for the binaries of OpenMixer's own packages, and a record whose verdict is \u003Ccode>refused\u003C\u002Fcode> refuses an add.\u003C\u002Fp>\n\u003Ch2 id=\"settings\">Settings\u003C\u002Fh2>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>key\u003C\u002Fth>\n\u003Cth>default\u003C\u002Fth>\n\u003Cth>meaning\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>pin_mode\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>first-use\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>strict\u003C\u002Fcode> or \u003Ccode>first-use\u003C\u002Fcode>, read only while \u003Ccode>require_pins\u003C\u002Fcode> is 1\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>pin_store\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>empty\u003C\u002Ftd>\n\u003Ctd>the file the learned pins are kept in; empty means \u003Ccode>$XDG_STATE_HOME\u002Fplugin-hostd\u002Fpins\u003C\u002Fcode>, else \u003Ccode>$HOME\u002F.local\u002Fstate\u002Fplugin-hostd\u002Fpins\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>pin_file\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>empty\u003C\u002Ftd>\n\u003Ctd>a pins file loaded at startup, written by \u003Ccode>plugin-qualify --all --pins\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>\u003Ccode>require_pins 0\u003C\u002Fcode> still means no pin is checked. \u003Ccode>strict\u003C\u002Fcode> is the earlier behaviour: a plugin with no pin is refused with\n-508. A host that pins by itself sets \u003Ccode>pin_mode strict\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"one-line-format\">One line format\u003C\u002Fh2>\n\u003Cp>The pin store, the pins file and the \u003Ccode>pin_set\u003C\u002Fcode> verb carry the same line:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>pin_set &lt;uri&gt; &lt;path&gt;=&lt;sha256&gt;[,&lt;path&gt;=&lt;sha256&gt;...] &lt;scheme&gt;:&lt;sha256&gt; [verdict=&lt;word&gt;]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n\u003Cli>In a file the leading \u003Ccode>pin_set\u003C\u002Fcode> is optional, \u003Ccode>#\u003C\u002Fcode> starts a comment and blank lines are skipped.\u003C\u002Fli>\n\u003Cli>The layout word is \u003Ccode>-\u003C\u002Fcode> while the layout has not been learned. \u003Ccode>pin_set\u003C\u002Fcode> as a verb refuses \u003Ccode>-\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Ccode>verdict=\u003C\u002Fcode> is the qualification verdict of the pinned bytes: \u003Ccode>in-process\u003C\u002Fcode>, \u003Ccode>isolated\u003C\u002Fcode> or \u003Ccode>refused\u003C\u002Fcode>. Absent means not\nqualified.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>The store holds only the pins the daemon learned or accepted. A pin that arrived by \u003Ccode>pin_set\u003C\u002Fcode> or \u003Ccode>pin_file\u003C\u002Fcode> is the\ncontroller's: it is not written to the store, and it replaces a stored one of the same uri while the daemon runs. The store\nis written to a temporary file in the same directory and renamed, mode 0600.\u003C\u002Fp>\n\u003Ch2 id=\"first-use\">First use\u003C\u002Fh2>\n\u003Cp>An \u003Ccode>add\u003C\u002Fcode> in \u003Ccode>first-use\u003C\u002Fcode> mode, with no pin for the uri:\u003C\u002Fp>\n\u003Col>\n\u003Cli>The daemon collects the files the format loads for the plugin, hashes them over open descriptors and records a pin with\nlayout \u003Ccode>-\u003C\u002Fcode> and no verdict. Nothing is accepted from a failed collection: the add is refused with -509 and no pin is\nrecorded.\u003C\u002Fli>\n\u003Cli>The instance is placed in a worker of its own, whatever the placement asked. Sharing a process needs a verdict of\n\u003Ccode>in-process\u003C\u002Fcode> on the pin, and a learned pin has none.\u003C\u002Fli>\n\u003Cli>Once the worker accepted the add, the daemon asks it for the parameter layout (\u003Ccode>pin_layout &lt;instance&gt;\u003C\u002Fcode>), records it in\nthe pin and writes the store. A worker that does not know the verb leaves the layout at \u003Ccode>-\u003C\u002Fcode>; the files are still\nchecked on every later add.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Later adds check the stored pin as \u003Ccode>strict\u003C\u002Fcode> does: the files are hashed before any worker sees the add, and the layout is\nhanded to the worker with \u003Ccode>pin_expect\u003C\u002Fcode>. A replayed add (a respawned worker) never learns: it checks the pin it has.\u003C\u002Fp>\n\u003Ch2 id=\"a-change\">A change\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>Files differ: the add answers -509. Layout differs: the worker refuses and the add answers -510. In both modes the\ndaemon emits \u003Ccode>pin_changed &lt;uri&gt; files\u003C\u002Fcode> or \u003Ccode>pin_changed &lt;uri&gt; layout\u003C\u002Fcode> on the feedback port.\u003C\u002Fli>\n\u003Cli>The pin stays as it was. Nothing loads the changed plugin until the controller decides.\u003C\u002Fli>\n\u003Cli>\u003Ccode>pin_accept &lt;uri&gt;\u003C\u002Fcode> takes the new pin: the files are collected and hashed again, recorded with layout \u003Ccode>-\u003C\u002Fcode> and without the\nverdict, so the next add learns the new layout and places the instance in a worker of its own. In \u003Ccode>strict\u003C\u002Fcode> mode it is\nrefused: a host that pins by itself changes a pin with \u003Ccode>pin_set\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Accepting never makes a plugin in-process: that stays a decision of qualification.\u003C\u002Fp>\n\u003Ch2 id=\"the-pins-file-of-plugin-qualify\">The pins file of plugin-qualify\u003C\u002Fh2>\n\u003Cp>\u003Ccode>plugin-qualify --all --pins &lt;file&gt;\u003C\u002Fcode> writes the same line, with \u003Ccode>verdict=\u003C\u002Fcode>, for every plugin it qualified. The daemon loads\nit at start with \u003Ccode>pin_file\u003C\u002Fcode>; a line it cannot parse stops the start and names the line. One command scans, qualifies and\npins what is installed; a corrupted hash in the file makes the add answer -509.\u003C\u002Fp>\n\u003Ch2 id=\"a-format-neutral-host\">A format-neutral host\u003C\u002Fh2>\n\u003Cp>The daemon keeps one table of formats, and a format is one row of it:\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>capability\u003C\u002Fth>\n\u003Cth>meaning\u003C\u002Fth>\n\u003Cth>lv2\u003C\u002Fth>\n\u003Cth>clap\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>name\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the word \u003Ccode>policy_set\u003C\u002Fcode>, \u003Ccode>worker_env\u003C\u002Fcode> and \u003Ccode>worker_list\u003C\u002Fcode> use\u003C\u002Ftd>\n\u003Ctd>lv2\u003C\u002Ftd>\n\u003Ctd>clap\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>prefix\u003C\u002Fcode>, \u003Ccode>strip_prefix\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>how a uri says it is this format, and whether the worker gets it without\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>lv2:\u003C\u002Fcode> stripped, also the default for a bare uri\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>clap:\u003C\u002Fcode> kept\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>worker\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the program, from the settings\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>mod_host\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>clap_host\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>pin_collect\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the root and the files the host loads for a uri\u003C\u002Ftd>\n\u003Ctd>the bundle's manifest, binary and seeAlso files\u003C\u002Ftd>\n\u003Ctd>the \u003Ccode>.clap\u003C\u002Fcode> file\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>layout_check\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the worker takes \u003Ccode>pin_expect\u003C\u002Fcode> and \u003Ccode>pin_layout\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>world_scope\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>what the worker's world is made of\u003C\u002Ftd>\n\u003Ctd>one bundle (narrowed)\u003C\u002Ftd>\n\u003Ctd>none\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>pin_pool\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>whether a pin lets the plugin share a worker\u003C\u002Ftd>\n\u003Ctd>no\u003C\u002Ftd>\n\u003Ctd>yes\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>The protocol names no format where it means a capability. A test registers a third format at run time and takes it\nthrough add, \u003Ccode>param_set\u003C\u002Fcode>, \u003Ccode>pin_set\u003C\u002Fcode>, \u003Ccode>pin_check\u003C\u002Fcode> and remove with the protocol untouched; a row with a capability missing is\nrefused with a named reason, so the test goes red instead of the daemon compiling it silently.\u003C\u002Fp>\n\u003Cp>What a standalone host does with an \u003Ccode>in-process\u003C\u002Fcode> verdict is the host's decision: the verdict travels in the pin.\u003C\u002Fp>\n",1793954995787]