From 7c6be04215af0324ca8331b996bee0e74fa9b6a4 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 21:46:26 +0200 Subject: [PATCH 01/17] Refuse the words worker_list, supervisor_reset and quit do not take protocol.h declares worker_list with no argument, supervisor_reset with at most one and mod-host's quit with none, but the daemon acted on any number of words after them. Each now answers ERR_INVALID_OPERATION to a word past its grammar, as mod-host refuses a command with too many arguments. --- src/verbs.c | 8 +++++++- tests/daemon_test.py | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/verbs.c b/src/verbs.c index cede3bc..78daab7 100644 --- a/src/verbs.c +++ b/src/verbs.c @@ -154,11 +154,15 @@ static char *handle(char *line, char **tok, int ntok) return atoi(tok[1]) == -1 ? sup_remove_all() : sup_remove(atoi(tok[1])); } if (!strcmp(verb, PHD_VERB_WORKER_LIST)) - return sup_worker_list(); + return ntok == 1 ? sup_worker_list() : sup_resp(ERR_INVALID_OPERATION); if (!strcmp(verb, PHD_VERB_INSTANCE_INFO)) return ntok == 2 ? sup_instance_info(atoi(tok[1])) : sup_resp(ERR_INVALID_OPERATION); if (!strcmp(verb, PHD_VERB_SUPERVISOR_RESET)) + { + if (ntok > 2) + return sup_resp(ERR_INVALID_OPERATION); return sup_reset(ntok > 1 ? tok[1] : PHD_WORD_ALL); + } if (!strcmp(verb, PHD_VERB_QUARANTINE_CLEAR)) return ntok == 2 ? sup_quarantine_clear(tok[1]) : sup_resp(ERR_INVALID_OPERATION); if (!strcmp(verb, PHD_VERB_POLICY_SET)) @@ -191,6 +195,8 @@ static char *handle(char *line, char **tok, int ntok) } if (verb_is(verb, QUIT)) { + if (ntok != 1) + return sup_resp(ERR_INVALID_OPERATION); g_quitting = 1; return sup_resp(SUCCESS); } diff --git a/tests/daemon_test.py b/tests/daemon_test.py index d94923a..32740d7 100644 --- a/tests/daemon_test.py +++ b/tests/daemon_test.py @@ -32,6 +32,20 @@ def ready_line_and_empty_table(): d.close() +@test +def a_word_past_the_grammar_of_a_daemon_verb_is_refused(): + d = daemon() + try: + # worker_list takes no word, supervisor_reset at most one and quit none, as protocol.h declares them + d.expect("worker_list w1", "resp -902") + d.expect("supervisor_reset all w1", "resp -902") + d.expect("quit now", "resp -902") + d.expect("worker_list", "resp 0") + d.expect("supervisor_reset all", "resp 0") + finally: + d.close() + + @test def placement_own_default_and_pool(): d = daemon() From 7d5441da746703a83d59e06d23e48db5336fa952 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 21:46:26 +0200 Subject: [PATCH 02/17] End a held gesture whole however long its line is The end of a gesture the relay holds was written through a 1024-byte buffer, so a gesture whose line was longer reached the controller cut short, without its end word, and the control stayed held. The held line and the end word now go out as two writes under the relay's lock, one line of any length. --- src/relay.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/relay.c b/src/relay.c index 029d663..b0bddfd 100644 --- a/src/relay.c +++ b/src/relay.c @@ -117,6 +117,14 @@ static void gesture_seen(source_t *s, const char *line) } } +/* the end of a held gesture, its line up to the last word and the word end, as one line of whatever length; g_lock is + * held, so nothing is written between the two parts */ +static void gesture_end(const char *held) +{ + send_line(held, strlen(held)); + send_line(" " PHD_GESTURE_END, sizeof(" " PHD_GESTURE_END)); +} + /* a worker that is gone ends every gesture it held, so no control stays held at the controller; g_lock is held */ static void gestures_end(source_t *s) { @@ -124,10 +132,7 @@ static void gestures_end(source_t *s) for (n = 0; n < s->nheld; n++) { - char line[1024]; - - snprintf(line, sizeof(line), "%s " PHD_GESTURE_END, s->held[n]); - send_line(line, strlen(line) + 1); + gesture_end(s->held[n]); free(s->held[n]); } free(s->held); @@ -148,15 +153,12 @@ static void gestures_end_instance(source_t *s, int id) plen = strlen(prefix); while (n < s->nheld) { - char line[1024]; - if (strncmp(s->held[n], prefix, plen)) { n++; continue; } - snprintf(line, sizeof(line), "%s " PHD_GESTURE_END, s->held[n]); - send_line(line, strlen(line) + 1); + gesture_end(s->held[n]); free(s->held[n]); s->held[n] = s->held[--s->nheld]; } From 3f4a044c3bd437baaa8f41d78963a2968342b472 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 21:48:40 +0200 Subject: [PATCH 03/17] Fuzz the command parser and dispatcher against a recorder fuzz/fuzz_command.c is a libFuzzer harness over src/verbs.c: a run of controller messages, each in an allocation of exactly its length, with everything below the dispatcher (supervisor, pins, descriptors, the socket's send) replaced by the recorder of fuzz/recorder.c. The recorder answers with the reply each verb declares in protocol.h, or with the refusal the input chose, and keeps a digest of what accepted calls changed. It checks four properties, each an abort(): one NUL-terminated reply of a declared shape per message, passed on unchanged; a verb the daemon parses is acted on only with an argument count its declared syntax allows; a command's words joined by one space make the same calls; and a refused command changes nothing. --- fuzz/fuzz.h | 92 ++++++++ fuzz/fuzz_command.c | 316 +++++++++++++++++++++++++ fuzz/recorder.c | 564 ++++++++++++++++++++++++++++++++++++++++++++ fuzz/recorder.h | 95 ++++++++ fuzz/syntax.h | 178 ++++++++++++++ 5 files changed, 1245 insertions(+) create mode 100644 fuzz/fuzz.h create mode 100644 fuzz/fuzz_command.c create mode 100644 fuzz/recorder.c create mode 100644 fuzz/recorder.h create mode 100644 fuzz/syntax.h diff --git a/fuzz/fuzz.h b/fuzz/fuzz.h new file mode 100644 index 0000000..f114e48 --- /dev/null +++ b/fuzz/fuzz.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * This file is part of plugin-hostd. + * + * plugin-hostd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * plugin-hostd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with plugin-hostd. If not, see . + * + * Copyright (C) 2026 Pau Aliagas + */ + +/* What the two libFuzzer harnesses share: a cursor over the input, the input-size refusal, and the exact-size copy + * that lets the sanitizer see a read one byte past a message. + * + * The cursor is total. Reading past the end of the input yields zeros and an empty slice, never a failure, so every + * input decodes to some sequence of calls and the end of the input ends it. Nothing here reads a clock, the + * environment, a file or the network. */ + +#ifndef PLUGIN_HOSTD_FUZZ_H +#define PLUGIN_HOSTD_FUZZ_H + +#include +#include +#include +#include + +/* the OSS Verify driver refuses an input this large; the harnesses refuse it too, so the domain they are proven over + * is the domain they are run over */ +#define FUZZ_INPUT_MAX ((size_t)64 << 20) + +struct fuzz_in { + const uint8_t *p; + size_t n; +}; + +static inline struct fuzz_in fuzz_in(const uint8_t *data, size_t size) +{ + struct fuzz_in in = { data, size }; + + return in; +} + +static inline uint8_t fuzz_u8(struct fuzz_in *in) +{ + if (in->n == 0) + return 0; + in->n--; + return *in->p++; +} + +/* little-endian, like every multi-byte field the harnesses invent */ +static inline uint16_t fuzz_u16(struct fuzz_in *in) +{ + uint16_t lo = fuzz_u8(in); + + return (uint16_t)(lo | (uint16_t)fuzz_u8(in) << 8); +} + +/* up to `want` bytes from the cursor; returns how many there were */ +static inline size_t fuzz_take(struct fuzz_in *in, size_t want, const uint8_t **out) +{ + size_t n = want < in->n ? want : in->n; + + *out = in->p; + in->p += n; + in->n -= n; + return n; +} + +/* a heap copy of exactly `n` bytes, with no terminator: a message handed to the daemon lives in its own allocation, so + * a read past its end lands in a redzone the sanitizer watches */ +static inline char *fuzz_dup(const uint8_t *p, size_t n) +{ + char *b = malloc(n ? n : 1); + + if (b == NULL) + abort(); + if (n) + memcpy(b, p, n); + return b; +} + +#endif diff --git a/fuzz/fuzz_command.c b/fuzz/fuzz_command.c new file mode 100644 index 0000000..cdcc04d --- /dev/null +++ b/fuzz/fuzz_command.c @@ -0,0 +1,316 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * This file is part of plugin-hostd. + * + * plugin-hostd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * plugin-hostd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with plugin-hostd. If not, see . + * + * Copyright (C) 2026 Pau Aliagas + */ + +/* libFuzzer harness: THE COMMAND PARSER AND DISPATCHER (src/verbs.c, verbs_receive and everything it calls in that + * file), with the mod-host side replaced by the recorder of fuzz/recorder.c. A controller's message is whatever bytes + * it sends; the daemon reads its words, refuses what is outside its grammar, and hands the rest to the supervisor. + * + * Input: up to MESSAGES_MAX messages, each + * [flags] [u16 LE length] [that many bytes, at most MESSAGE_MAX] + * flags bit 0: the recorder refuses every call of this message that can refuse, with the declared code + * rec_codes[(flags >> 1) % rec_ncodes]; clear, it accepts with the reply each verb declares. Each message is + * copied into an allocation of exactly its length, with no terminator, as the socket hands it over. + * + * The words of a message, below, are what the daemon reads: its bytes up to the first NUL, the newlines, carriage + * returns and spaces at its end removed, split at spaces and tabs. Its verb is the first word. + * + * Properties, each an abort(): + * - reply: every message gets exactly one reply, one NUL-terminated string with no NUL before its end, which is + * the text the supervisor answered, unchanged; a refusal is "resp " of a declared code, and anything else + * has the reply shape its verb declares (PHD_VERBS, PHD_INSTANCE_VERBS) or mod-host's "resp 0". + * Mutant: fuzz/mutants/command-reply.patch, the reply sent without its NUL. + * - arity: a verb the daemon parses itself (PHD_VERBS, the rows tools/protocol-gen.c writes into + * protocol/plugin-hostd.json, and mod-host's remove, connect, disconnect and quit) is acted on only with an + * argument count its declared syntax allows; a word past PHD_LINE_WORDS_MAX is not read. + * Mutant: fuzz/mutants/command-arity.patch, pool_config taking three words or more. + * - round-trip: the words of a command the daemon acted on, joined by one space as the declaration writes them, + * make the same calls with the same arguments, the same reply and the same state. A command whose last word ends + * in a carriage return or a newline is left out: the daemon keeps that character when a tab follows it and + * removes it when the line ends there, so its joined words are another command. + * Mutant: fuzz/mutants/command-round-trip.patch, words split at spaces only. + * - refusal: a refused command (its reply's code is negative) leaves the daemon's state as it was: no call the + * supervisor took and none of the daemon's own follow-ups (a descriptor scan, the LV2 refusals asked again, quit). + * The host settings are not part of it: PHD_HOST_SETTINGS keeps them as sent, whatever the workers answer. + * Mutant: fuzz/mutants/command-refusal.patch, a refused add still scanned. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "supervisor.h" +#include "verbs.h" + +#include "fuzz.h" +#include "recorder.h" +#include "syntax.h" + +#define MESSAGES_MAX 32 +#define MESSAGE_MAX (2 * PHD_LINE_MAX) + +/* the verbs the daemon parses itself, with their declared argument syntax */ +static const struct { + const char *name; + const char *syntax; + int format; /* 1: syntax is a mod-host command format, " %i ..." */ +} g_parsed[] = { +#define X(id, name, arguments, reply, meaning) { name, arguments, 0 }, + PHD_VERBS(X) +#undef X + { "remove", EFFECT_REMOVE, 1 }, + { "connect", EFFECT_CONNECT, 1 }, + { "disconnect", EFFECT_DISCONNECT, 1 }, + { "quit", QUIT, 1 }, +}; + +/* the words of a message as the daemon reads them */ +struct words { + char *text; /* the message up to its first NUL, its end trimmed; the words point into a copy */ + char *copy; + char **w; + int n; +}; + +static void words_of(const char *data, size_t size, struct words *out) +{ + size_t len = strnlen(data, size), cap = len / 2 + 2; + char *t; + + out->text = malloc(len + 1); + out->copy = malloc(len + 1); + out->w = malloc(cap * sizeof(char *)); + if (!out->text || !out->copy || !out->w) + abort(); + memcpy(out->text, data, len); + out->text[len] = '\0'; + while (len > 0 && (out->text[len - 1] == '\n' || out->text[len - 1] == '\r' || out->text[len - 1] == ' ')) + out->text[--len] = '\0'; + memcpy(out->copy, out->text, len + 1); + out->n = 0; + for (t = out->copy; *t;) + { + t += strspn(t, " \t"); + if (!*t) + break; + out->w[out->n++] = t; + t += strcspn(t, " \t"); + if (*t) + *t++ = '\0'; + } +} + +static void words_free(struct words *w) +{ + free(w->text); + free(w->copy); + free(w->w); +} + +/* the message's words joined by one space */ +static char *joined(const struct words *w, size_t *len) +{ + size_t n = 0; + char *s; + int k; + + for (k = 0; k < w->n; k++) + n += strlen(w->w[k]) + 1; + s = malloc(n + 1); + if (!s) + abort(); + s[0] = '\0'; + for (k = 0, n = 0; k < w->n; k++) + { + size_t l = strlen(w->w[k]); + + if (k) + s[n++] = ' '; + memcpy(s + n, w->w[k], l); + n += l; + } + s[n] = '\0'; + *len = n; + return s; +} + +/* one message to the daemon, in an allocation of exactly its length */ +static void send_message(const uint8_t *bytes, size_t len) +{ + msg_t msg; + + msg.sender_id = 7; + msg.data = fuzz_dup(bytes, len); + msg.data_size = len; + g_quitting = 0; + rec_clear(); + verbs_receive(&msg); + free(msg.data); +} + +/* whether the daemon acted on the message: it called the supervisor, or it is quitting */ +static int acted(void) +{ + return g_quitting || g_rec.ncalls > 0; +} + +/* the reply property */ +__attribute__((noinline)) static void check_reply(const struct words *w) +{ + char refusal[32]; + const char *shape; + int code; + + /* one reply, and it is a string that ends where the message the socket sends ends */ + if (g_rec.nreplies != 1 || g_rec.reply_size == 0) + abort(); + if (g_rec.reply[g_rec.reply_size - 1] != '\0' || memchr(g_rec.reply, '\0', g_rec.reply_size - 1)) + abort(); + /* the supervisor's answer, as it answered it */ + if (g_rec.answer == NULL || strcmp(g_rec.reply, g_rec.answer)) + abort(); + code = sup_reply_code(g_rec.reply); + if (code < 0) + { + snprintf(refusal, sizeof(refusal), "resp %d", code); + if (!rec_code_declared(code) || strcmp(g_rec.reply, refusal)) + abort(); + return; + } + shape = w->n ? rec_shape_of(w->w[0], strlen(w->w[0])) : REC_MOD_HOST_SHAPE; + if (!syntax_reply_matches(shape, g_rec.reply) && !syntax_reply_matches(REC_MOD_HOST_SHAPE, g_rec.reply)) + abort(); +} + +/* the arity property */ +__attribute__((noinline)) static void check_arity(const struct words *w) +{ + size_t n; + int read, min, max; + + if (w->n == 0 || !acted() || (g_rec.ncalls > 0 && g_rec.calls[0].fn == REC_BROADCAST)) + return; + read = w->n < PHD_LINE_WORDS_MAX ? w->n : PHD_LINE_WORDS_MAX; + for (n = 0; n < sizeof(g_parsed) / sizeof(g_parsed[0]); n++) + { + if (strcmp(w->w[0], g_parsed[n].name)) + continue; + if (g_parsed[n].format) + syntax_format_arity(g_parsed[n].syntax, &min, &max); + else + syntax_arity(g_parsed[n].syntax, &min, &max); + /* acted on with an argument count the declaration allows */ + if (read - 1 < min || read - 1 > max) + abort(); + return; + } +} + +/* the refusal property */ +__attribute__((noinline)) static void check_refusal(uint64_t before) +{ + if (g_rec.reply == NULL || sup_reply_code(g_rec.reply) >= 0) + return; + /* nothing changed: no accepted call, no follow-up, no quit */ + if (g_rec.state != before || g_quitting) + abort(); +} + +/* the round-trip property: the message has been sent, and the daemon acted on it */ +__attribute__((noinline)) static void check_round_trip(const struct words *w, uint64_t state_before, + uint64_t host_before) +{ + struct rec_call first[REC_CALLS_MAX]; + int nfirst, quit_first; + uint64_t state_first, host_first; + char *reply_first, *line; + const char *last; + size_t len; + + if (w->n == 0 || !acted()) + return; + last = w->w[w->n - 1]; + if (last[strlen(last) - 1] == '\r' || last[strlen(last) - 1] == '\n') + return; + nfirst = rec_calls_copy(first); + quit_first = g_quitting; + state_first = g_rec.state; + host_first = g_rec.host_settings; + reply_first = strdup(g_rec.reply); + if (!reply_first) + abort(); + + /* the same command again, from the state it was sent in, as its words joined by one space */ + g_rec.state = state_before; + g_rec.host_settings = host_before; + line = joined(w, &len); + send_message((const uint8_t *)line, len); + free(line); + + if (!rec_calls_equal(first, nfirst, g_rec.calls, g_rec.ncalls)) + abort(); + if (g_rec.reply == NULL || strcmp(g_rec.reply, reply_first)) + abort(); + if (g_quitting != quit_first || g_rec.state != state_first || g_rec.host_settings != host_first) + abort(); + rec_calls_free(first, nfirst); + free(reply_first); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + struct fuzz_in in; + int m; + + if (size >= FUZZ_INPUT_MAX) + return 0; + in = fuzz_in(data, size); + rec_clear(); + g_rec.state = 0; + g_rec.host_settings = 0; + for (m = 0; m < MESSAGES_MAX && in.n; m++) + { + uint8_t flags = fuzz_u8(&in); + uint16_t want = fuzz_u16(&in); + const uint8_t *bytes; + size_t len = fuzz_take(&in, want < MESSAGE_MAX ? want : MESSAGE_MAX, &bytes); + uint64_t state_before = g_rec.state, host_before = g_rec.host_settings; + struct words w; + + g_rec.refuse = flags & 1; + g_rec.code = rec_codes[(flags >> 1) % rec_ncodes]; + send_message(bytes, len); + words_of((const char *)bytes, len, &w); + check_reply(&w); + check_arity(&w); + check_refusal(state_before); + check_round_trip(&w, state_before, host_before); + words_free(&w); + } + rec_clear(); + g_quitting = 0; + return 0; +} diff --git a/fuzz/recorder.c b/fuzz/recorder.c new file mode 100644 index 0000000..ef9a034 --- /dev/null +++ b/fuzz/recorder.c @@ -0,0 +1,564 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * This file is part of plugin-hostd. + * + * plugin-hostd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * plugin-hostd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with plugin-hostd. If not, see . + * + * Copyright (C) 2026 Pau Aliagas + */ + +/* The recorder that stands in for everything below src/verbs.c (fuzz/recorder.h). */ + +#include +#include +#include + +#include +#include +#include + +#include + +#include "descriptors.h" +#include "formats.h" +#include "pins.h" +#include "supervisor.h" + +#include "recorder.h" +#include "syntax.h" + +struct recorder g_rec; + +const int rec_codes[] = { + ERR_INSTANCE_INVALID, ERR_INSTANCE_ALREADY_EXISTS, ERR_INSTANCE_NON_EXISTS, ERR_INSTANCE_UNLICENSED, + ERR_HOST_INVALID_URI, ERR_HOST_INSTANTIATION, ERR_HOST_INVALID_PARAM_SYMBOL, ERR_HOST_INVALID_PRESET_URI, + ERR_HOST_CANT_LOAD_STATE, ERR_JACK_CLIENT_CREATION, ERR_JACK_CLIENT_ACTIVATION, ERR_JACK_CLIENT_DEACTIVATION, + ERR_JACK_PORT_REGISTER, ERR_JACK_PORT_CONNECTION, ERR_JACK_PORT_DISCONNECTION, ERR_JACK_VALUE_OUT_OF_RANGE, + ERR_ASSIGNMENT_ALREADY_EXISTS, ERR_ASSIGNMENT_INVALID_OP, ERR_ASSIGNMENT_LIST_FULL, ERR_ASSIGNMENT_FAILED, + ERR_ASSIGNMENT_UNUSED, ERR_CONTROL_CHAIN_UNAVAILABLE, ERR_ABLETON_LINK_UNAVAILABLE, ERR_HMI_UNAVAILABLE, + ERR_EXTERNAL_UI_UNAVAILABLE, ERR_MEMORY_ALLOCATION, ERR_INVALID_OPERATION, +#define X(id, meaning) PHD_ERR_##id, + PHD_ERRORS(X) +#undef X +}; +const int rec_ncodes = (int)(sizeof(rec_codes) / sizeof(rec_codes[0])); + +int rec_code_declared(int code) +{ + int n; + + for (n = 0; n < rec_ncodes; n++) + if (rec_codes[n] == code) + return 1; + return 0; +} + +/* the reply shapes the daemon's verbs declare (PHD_VERBS, PHD_INSTANCE_VERBS) */ +static const struct { const char *name, *reply; } g_shapes[] = { +#define X(id, name, arguments, reply, meaning) { name, reply }, + PHD_VERBS(X) +#undef X +#define X(id, name, arguments, reply, ledger, meaning) { name, reply }, + PHD_INSTANCE_VERBS(X) +#undef X +}; + +const char *rec_shape_of(const char *verb, size_t len) +{ + size_t n; + + for (n = 0; n < sizeof(g_shapes) / sizeof(g_shapes[0]); n++) + if (strlen(g_shapes[n].name) == len && !memcmp(g_shapes[n].name, verb, len)) + return g_shapes[n].reply; + return REC_MOD_HOST_SHAPE; +} + +static void *xmalloc(size_t n) +{ + void *p = malloc(n ? n : 1); + + if (p == NULL) + abort(); + return p; +} + +static char *xstrdup(const char *s) +{ + size_t n = strlen(s) + 1; + + return memcpy(xmalloc(n), s, n); +} + +char *rec_canonical(const char *text, size_t len) +{ + char *out = xmalloc(len + 1); + size_t i = 0, o = 0; + + while (i < len) + { + while (i < len && (text[i] == ' ' || text[i] == '\t')) + i++; + if (i == len) + break; + if (o) + out[o++] = ' '; + while (i < len && text[i] != ' ' && text[i] != '\t') + out[o++] = text[i++]; + } + out[o] = '\0'; + return out; +} + +void rec_clear(void) +{ + rec_calls_free(g_rec.calls, g_rec.ncalls); + g_rec.ncalls = 0; + free(g_rec.answer); + g_rec.answer = NULL; + free(g_rec.reply); + g_rec.reply = NULL; + g_rec.reply_size = 0; + g_rec.nreplies = 0; +} + +void rec_calls_free(struct rec_call *calls, int n) +{ + int i, a; + + for (i = 0; i < n; i++) + { + for (a = 0; a < calls[i].nargs; a++) + free(calls[i].args[a]); + calls[i].nargs = 0; + } +} + +int rec_calls_copy(struct rec_call *dst) +{ + int i, a; + + for (i = 0; i < g_rec.ncalls; i++) + { + dst[i].fn = g_rec.calls[i].fn; + dst[i].nargs = g_rec.calls[i].nargs; + for (a = 0; a < dst[i].nargs; a++) + dst[i].args[a] = xstrdup(g_rec.calls[i].args[a]); + } + return g_rec.ncalls; +} + +int rec_calls_equal(const struct rec_call *a, int na, const struct rec_call *b, int nb) +{ + int i, k; + + if (na != nb) + return 0; + for (i = 0; i < na; i++) + { + if (a[i].fn != b[i].fn || a[i].nargs != b[i].nargs) + return 0; + for (k = 0; k < a[i].nargs; k++) + if (strcmp(a[i].args[k], b[i].args[k])) + return 0; + } + return 1; +} + +/* FNV-1a, 64 bits */ +static uint64_t mix(uint64_t h, const void *p, size_t n) +{ + const unsigned char *b = p; + + if (h == 0) + h = 0xcbf29ce484222325ull; + while (n--) + { + h ^= *b++; + h *= 0x100000001b3ull; + } + return h; +} + +static uint64_t mix_call(uint64_t h, const struct rec_call *c) +{ + int a; + + h = mix(h, &c->fn, sizeof(c->fn)); + for (a = 0; a < c->nargs; a++) + h = mix(h, c->args[a], strlen(c->args[a]) + 1); + return h; +} + +/* a call, recorded with its arguments; returns it for the caller to fold into the state */ +static struct rec_call *record(int fn) +{ + static struct rec_call spill; + struct rec_call *c; + + if (g_rec.ncalls == REC_CALLS_MAX) + { + /* past the table: kept apart, so a runaway message still changes the state it would change */ + rec_calls_free(&spill, 1); + c = &spill; + } + else + c = &g_rec.calls[g_rec.ncalls++]; + c->fn = fn; + c->nargs = 0; + return c; +} + +static void arg_s(struct rec_call *c, const char *s) +{ + if (c->nargs < REC_ARGS_MAX) + c->args[c->nargs++] = xstrdup(s ? s : "(null)"); +} + +static void arg_line(struct rec_call *c, const char *line) +{ + if (c->nargs < REC_ARGS_MAX) + c->args[c->nargs++] = rec_canonical(line, strlen(line)); +} + +static void arg_i(struct rec_call *c, int v) +{ + char b[16]; + + snprintf(b, sizeof(b), "%d", v); + arg_s(c, b); +} + +static char *hand_back(const char *text) +{ + free(g_rec.answer); + g_rec.answer = xstrdup(text); + return xstrdup(text); +} + +/* the answer to a call that can refuse: the input's refusal, or the verb's declared reply, which changes the state + * when the call changes anything */ +static char *answer(struct rec_call *c, const char *shape, int changes) +{ + char buf[1024]; + + if (g_rec.refuse) + { + snprintf(buf, sizeof(buf), "resp %d", g_rec.code); + return hand_back(buf); + } + if (changes) + g_rec.state = mix_call(g_rec.state, c); + if (syntax_instantiate(shape, buf, sizeof(buf)) < 0) + abort(); + return hand_back(buf); +} + +static const char *daemon_shape(const char *verb) +{ + return rec_shape_of(verb, strlen(verb)); +} + +/* ---------------------------------------------------------------- the supervisor (src/supervisor.h) */ + +char *sup_resp(int code) +{ + char buf[32]; + + snprintf(buf, sizeof(buf), "resp %d", code); + return hand_back(buf); +} + +int sup_reply_code(const char *reply) +{ + return strncmp(reply, "resp ", 5) == 0 ? atoi(reply + 5) : -999; +} + +char *sup_add(const char *uri, int id, const char *placement, const char *client) +{ + struct rec_call *c = record(REC_ADD); + char buf[32]; + + arg_s(c, uri); + arg_i(c, id); + arg_s(c, placement ? placement : "-"); + arg_s(c, client ? client : "-"); + /* the supervisor takes an instance number of its table only, and answers it back (PHD_VERBS add) */ + if (id < 0 || id >= MAX_INSTANCE) + { + snprintf(buf, sizeof(buf), "resp %d", ERR_INSTANCE_INVALID); + return hand_back(buf); + } + if (g_rec.refuse) + return answer(c, NULL, 0); + g_rec.state = mix_call(g_rec.state, c); + snprintf(buf, sizeof(buf), "resp %d", id); + return hand_back(buf); +} + +char *sup_call(int id, const char *line) +{ + struct rec_call *c = record(REC_CALL); + + arg_i(c, id); + arg_line(c, line); + return answer(c, REC_MOD_HOST_SHAPE, 1); +} + +char *sup_remove(int id) +{ + struct rec_call *c = record(REC_REMOVE); + + arg_i(c, id); + return answer(c, REC_MOD_HOST_SHAPE, 1); +} + +char *sup_remove_all(void) +{ + return answer(record(REC_REMOVE_ALL), REC_MOD_HOST_SHAPE, 1); +} + +char *sup_connect(const char *verb, const char *line, const char *port_a, const char *port_b) +{ + struct rec_call *c = record(REC_CONNECT); + + arg_s(c, verb); + arg_line(c, line); + arg_s(c, port_a); + arg_s(c, port_b); + return answer(c, REC_MOD_HOST_SHAPE, 1); +} + +char *sup_broadcast(const char *line) +{ + struct rec_call *c = record(REC_BROADCAST); + + arg_line(c, line); + return answer(c, REC_MOD_HOST_SHAPE, 1); +} + +void sup_host_setting(const char *line, char **tok, int ntok) +{ + struct rec_call *c = record(REC_HOST_SETTING); + size_t len = 0; + char *words; + int n; + + arg_line(c, line); + arg_i(c, ntok); + for (n = 0; n < ntok; n++) + len += strlen(tok[n]) + 1; + words = xmalloc(len + 1); + words[0] = '\0'; + for (n = 0; n < ntok; n++) + { + if (n) + strcat(words, " "); + strcat(words, tok[n]); + } + arg_s(c, words); + free(words); + g_rec.host_settings = mix_call(g_rec.host_settings, c); +} + +char *sup_instance_verb(const char *line, int ledger) +{ + struct rec_call *c = record(REC_INSTANCE_VERB); + + arg_line(c, line); + arg_i(c, ledger); + /* the verb is the first word of the line, past any space before it */ + line += strspn(line, " \t"); + return answer(c, rec_shape_of(line, strcspn(line, " \t")), 1); +} + +char *sup_worker_list(void) +{ + return answer(record(REC_WORKER_LIST), daemon_shape(PHD_VERB_WORKER_LIST), 0); +} + +char *sup_instance_info(int id) +{ + struct rec_call *c = record(REC_INSTANCE_INFO); + + arg_i(c, id); + return answer(c, daemon_shape(PHD_VERB_INSTANCE_INFO), 0); +} + +char *sup_reset(const char *which) +{ + struct rec_call *c = record(REC_RESET); + + arg_s(c, which); + return answer(c, daemon_shape(PHD_VERB_SUPERVISOR_RESET), 1); +} + +char *sup_quarantine_clear(const char *which) +{ + struct rec_call *c = record(REC_QUARANTINE_CLEAR); + + arg_s(c, which); + return answer(c, daemon_shape(PHD_VERB_QUARANTINE_CLEAR), 1); +} + +char *sup_policy_set(const char *format, const char *place) +{ + struct rec_call *c = record(REC_POLICY_SET); + + arg_s(c, format); + arg_s(c, place); + return answer(c, daemon_shape(PHD_VERB_POLICY_SET), 1); +} + +char *sup_pin_accept(const char *uri) +{ + struct rec_call *c = record(REC_PIN_ACCEPT); + + arg_s(c, uri); + return answer(c, daemon_shape(PHD_VERB_PIN_ACCEPT), 1); +} + +char *sup_pool_config(const char *name, int max) +{ + struct rec_call *c = record(REC_POOL_CONFIG); + + arg_s(c, name); + arg_i(c, max); + return answer(c, daemon_shape(PHD_VERB_POOL_CONFIG), 1); +} + +char *sup_lv2_worker_set(const char *uri, const char *worker) +{ + struct rec_call *c = record(REC_LV2_WORKER_SET); + + arg_s(c, uri); + arg_s(c, worker); + return answer(c, daemon_shape(PHD_VERB_LV2_WORKER_SET), 1); +} + +char *sup_worker_env(const char *format, const char *cpus, const char *nice) +{ + struct rec_call *c = record(REC_WORKER_ENV); + + arg_s(c, format); + arg_s(c, cpus); + arg_s(c, nice); + return answer(c, daemon_shape(PHD_VERB_WORKER_ENV), 1); +} + +char *sup_chain(int id, const int *ids, int n) +{ + struct rec_call *c = record(REC_CHAIN); + char *list = xmalloc((size_t)n * 12 + 1); + size_t o = 0; + int k; + + arg_i(c, id); + arg_i(c, n); + list[0] = '\0'; + for (k = 0; k < n; k++) + o += (size_t)sprintf(list + o, k ? " %d" : "%d", ids[k]); + arg_s(c, list); + free(list); + return answer(c, daemon_shape(PHD_VERB_CHAIN), 1); +} + +char *sup_chain_clear(int id) +{ + struct rec_call *c = record(REC_CHAIN_CLEAR); + + arg_i(c, id); + return answer(c, daemon_shape(PHD_VERB_CHAIN_CLEAR), 1); +} + +/* a checkpoint of what a preset or patch changed: it writes down state, it changes none */ +void sup_after_reply(int id, const char *verb) +{ + struct rec_call *c = record(REC_AFTER_REPLY); + + arg_i(c, id); + arg_s(c, verb); +} + +/* ---------------------------------------------------------------- the pins (src/pins.h) */ + +int pins_set(const char *uri, const char *files, const char *layout, const char *verdict) +{ + struct rec_call *c = record(REC_PINS_SET); + + arg_s(c, uri); + arg_s(c, files); + arg_s(c, layout); + arg_s(c, verdict ? verdict : "-"); + if (g_rec.refuse) + return g_rec.code; + g_rec.state = mix_call(g_rec.state, c); + return SUCCESS; +} + +int pins_clear(const char *uri) +{ + struct rec_call *c = record(REC_PINS_CLEAR); + + arg_s(c, uri); + if (g_rec.refuse) + return g_rec.code; + g_rec.state = mix_call(g_rec.state, c); + return SUCCESS; +} + +/* ---------------------------------------------------------------- the descriptors and the LV2 refusals */ + +/* a scan of the plugin's files starts: a change whatever the add was answered */ +void descriptors_add(const char *uri) +{ + struct rec_call *c = record(REC_DESCRIPTORS_ADD); + + arg_s(c, uri); + g_rec.state = mix_call(g_rec.state, c); +} + +char *descriptors_get(const char *uri) +{ + struct rec_call *c = record(REC_DESCRIPTORS_GET); + + arg_s(c, uri); + return answer(c, daemon_shape(PHD_VERB_DESCRIPTOR_GET), 0); +} + +/* the adapter is asked again about what it refused: a change */ +void lv2_refusals_forget(const char *uri) +{ + struct rec_call *c = record(REC_LV2_REFUSALS_FORGET); + + arg_s(c, uri ? uri : "*"); + g_rec.state = mix_call(g_rec.state, c); +} + +/* ---------------------------------------------------------------- the command socket (mod-host's socket.h) */ + +int socket_send(int destination, const char *buffer, int size) +{ + (void)destination; + g_rec.nreplies++; + free(g_rec.reply); + g_rec.reply = NULL; + g_rec.reply_size = 0; + if (size > 0) + { + g_rec.reply = xmalloc((size_t)size); + memcpy(g_rec.reply, buffer, (size_t)size); + g_rec.reply_size = (size_t)size; + } + return size; +} diff --git a/fuzz/recorder.h b/fuzz/recorder.h new file mode 100644 index 0000000..bc27bb1 --- /dev/null +++ b/fuzz/recorder.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * This file is part of plugin-hostd. + * + * plugin-hostd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * plugin-hostd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with plugin-hostd. If not, see . + * + * Copyright (C) 2026 Pau Aliagas + */ + +/* The mod-host side of the daemon, replaced by a recorder for fuzz/fuzz_command.c: every function src/verbs.c calls + * below it (the supervisor, the pins, the descriptors, the LV2 refusals and the socket's send) is defined here. Each + * records the call it was handed, answers with the reply its verb declares, or with the refusal the input chose, and + * folds what an accepted call changes into a digest of the daemon's state. No socket, no process, no JACK, no file. */ + +#ifndef PLUGIN_HOSTD_FUZZ_RECORDER_H +#define PLUGIN_HOSTD_FUZZ_RECORDER_H + +#include +#include + +enum { + REC_ADD, REC_CALL, REC_REMOVE, REC_REMOVE_ALL, REC_CONNECT, REC_BROADCAST, REC_HOST_SETTING, REC_INSTANCE_VERB, + REC_WORKER_LIST, REC_INSTANCE_INFO, REC_RESET, REC_QUARANTINE_CLEAR, REC_POLICY_SET, REC_PIN_ACCEPT, + REC_POOL_CONFIG, REC_LV2_WORKER_SET, REC_WORKER_ENV, REC_CHAIN, REC_CHAIN_CLEAR, REC_AFTER_REPLY, REC_PINS_SET, + REC_PINS_CLEAR, REC_DESCRIPTORS_ADD, REC_DESCRIPTORS_GET, REC_LV2_REFUSALS_FORGET, + REC_FN_COUNT +}; + +#define REC_CALLS_MAX 16 +#define REC_ARGS_MAX 6 + +/* one call, its arguments as text: a number as %d, a line as its words joined by one space */ +struct rec_call { + int fn; + int nargs; + char *args[REC_ARGS_MAX]; +}; + +struct recorder { + /* chosen by the input for the message: refuse every call that can refuse, with this declared code */ + int refuse; + int code; + + /* what the message did */ + int ncalls; + struct rec_call calls[REC_CALLS_MAX]; + char *answer; /* a copy of the last reply text handed back to the daemon */ + int nreplies; /* socket_send calls */ + char *reply; /* the bytes of the last one */ + size_t reply_size; + + /* the daemon's state: a digest of every change an accepted call made */ + uint64_t state; + /* the host settings, which are kept as they were sent whatever the workers answer (PHD_HOST_SETTINGS) */ + uint64_t host_settings; +}; + +extern struct recorder g_rec; + +/* the declared refusal codes: mod-host's (host-errors.h) and the daemon's own (PHD_ERRORS) */ +extern const int rec_codes[]; +extern const int rec_ncodes; +int rec_code_declared(int code); + +/* mod-host answers a command it took "resp 0" */ +#define REC_MOD_HOST_SHAPE "resp 0" + +/* the declared reply shape of a verb of the daemon (PHD_VERBS, PHD_INSTANCE_VERBS), else mod-host's */ +const char *rec_shape_of(const char *verb, size_t len); + +/* forget what the last message did; the state stays */ +void rec_clear(void); + +/* whether two messages did the same: the same calls with the same arguments, in the same order */ +int rec_calls_equal(const struct rec_call *a, int na, const struct rec_call *b, int nb); + +/* a deep copy of the calls of the message, and its release */ +int rec_calls_copy(struct rec_call *dst); +void rec_calls_free(struct rec_call *calls, int n); + +/* the words of `text` up to `len` bytes, at spaces and tabs, joined by one space; malloc'd */ +char *rec_canonical(const char *text, size_t len); + +#endif diff --git a/fuzz/syntax.h b/fuzz/syntax.h new file mode 100644 index 0000000..d9bd1c0 --- /dev/null +++ b/fuzz/syntax.h @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * This file is part of plugin-hostd. + * + * plugin-hostd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * plugin-hostd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with plugin-hostd. If not, see . + * + * Copyright (C) 2026 Pau Aliagas + */ + +/* The declared syntax of include/plugin-hostd/protocol.h read as words, the way tools/protocol-gen.c writes it into + * README.md and protocol/plugin-hostd.json: the arguments of a verb (" [own | pool: | default]"), + * the shape of its reply ("resp 0 "), and mod-host's command formats ("remove %i", "add %s %i ..."). + * + * An item of a syntax is a run of characters with no space outside <...> and [...]. "a | b" at the top is one word + * that is a or b; [...] is a word that may be left out, and one holding "..." may repeat without end. */ + +#ifndef PLUGIN_HOSTD_FUZZ_SYNTAX_H +#define PLUGIN_HOSTD_FUZZ_SYNTAX_H + +#include +#include +#include + +#define SYNTAX_UNBOUNDED INT_MAX + +/* the item that starts at or after *s, its length in *len; NULL when there is none */ +static inline const char *syntax_item(const char **s, size_t *len) +{ + const char *p = *s, *start; + int depth = 0; + + while (*p == ' ') + p++; + if (!*p) + return NULL; + start = p; + for (; *p && (depth > 0 || *p != ' '); p++) + { + if (*p == '<' || *p == '[') + depth++; + else if ((*p == '>' || *p == ']') && depth > 0) + depth--; + } + *len = (size_t)(p - start); + *s = p; + return start; +} + +static inline int syntax_has(const char *item, size_t len, const char *what) +{ + size_t n = strlen(what), i; + + for (i = 0; i + n <= len; i++) + if (!memcmp(item + i, what, n)) + return 1; + return 0; +} + +/* the fewest and the most words a declared argument syntax takes */ +static inline void syntax_arity(const char *syntax, int *min, int *max) +{ + const char *s = syntax, *item; + size_t len; + + *min = *max = 0; + while ((item = syntax_item(&s, &len))) + { + if (len == 1 && *item == '|') + { + /* the alternative of the word before it: no word of its own */ + syntax_item(&s, &len); + continue; + } + if (item[0] == '[' && item[len - 1] == ']') + { + if (syntax_has(item, len, "...")) + *max = SYNTAX_UNBOUNDED; + else if (*max != SYNTAX_UNBOUNDED) + (*max)++; + continue; + } + (*min)++; + if (*max != SYNTAX_UNBOUNDED) + (*max)++; + } +} + +/* the fewest and the most arguments of a mod-host command format, " %i %s ...": a %-word each, "..." for a tail + * of any length */ +static inline void syntax_format_arity(const char *format, int *min, int *max) +{ + const char *s = format, *item; + size_t len; + + *min = *max = 0; + syntax_item(&s, &len); + while ((item = syntax_item(&s, &len))) + { + if (len == 3 && !memcmp(item, "...", 3)) + *max = SYNTAX_UNBOUNDED; + else + { + (*min)++; + if (*max != SYNTAX_UNBOUNDED) + (*max)++; + } + } +} + +/* a reply of the declared shape: the template with every <...> written as 1, into buf; the length, or -1 when it does + * not fit */ +static inline int syntax_instantiate(const char *shape, char *buf, size_t size) +{ + size_t o = 0; + int depth = 0; + + for (; *shape; shape++) + { + char c = *shape; + + if (c == '<') + { + if (depth++ == 0) + c = '1'; + else + continue; + } + else if (c == '>' && depth > 0) + { + depth--; + continue; + } + else if (depth > 0) + continue; + if (o + 1 >= size) + return -1; + buf[o++] = c; + } + buf[o] = '\0'; + return (int)o; +} + +/* whether a reply has the declared shape: as many words as the shape has items, each literal item spelled the same, an + * item holding a <...> standing for any one word */ +static inline int syntax_reply_matches(const char *shape, const char *reply) +{ + const char *s = shape, *item; + size_t len; + + while ((item = syntax_item(&s, &len))) + { + size_t w = strcspn(reply, " "); + + if (w == 0) + return 0; + if (!syntax_has(item, len, "<") && (w != len || memcmp(item, reply, len))) + return 0; + reply += w; + if (*reply == ' ' && reply[1]) + reply++; + else if (*reply) + return 0; + } + return *reply == '\0'; +} + +#endif From 0d5a695a6bcf69da3c92d683363c86db0aeb0d91 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 21:48:40 +0200 Subject: [PATCH 04/17] Fuzz the reply stream reader against a reference splitter fuzz/fuzz_reply.c includes src/relay.c, as tests/gesture_alloc_test.c does, and answers its recv and send itself: the input chooses what each worker writes, where each read cuts it, when a worker closes or is removed, which instance goes, and how the controller's socket takes the lines. A reference splitter reads the same bytes. Its properties: the buffer of an unfinished line stays bounded; the controller reads every complete line once, in order, however the stream was cut; and every line the relay writes of its own is an event as written or the end of a gesture still held. The relay's line limit can now be set at build time, so the harness reaches the drop of a long line with inputs of a few kilobytes. --- fuzz/fuzz_reply.c | 682 ++++++++++++++++++++++++++++++++++++++++++++++ src/relay.c | 5 +- 2 files changed, 686 insertions(+), 1 deletion(-) create mode 100644 fuzz/fuzz_reply.c diff --git a/fuzz/fuzz_reply.c b/fuzz/fuzz_reply.c new file mode 100644 index 0000000..114e05f --- /dev/null +++ b/fuzz/fuzz_reply.c @@ -0,0 +1,682 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * This file is part of plugin-hostd. + * + * plugin-hostd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * plugin-hostd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with plugin-hostd. If not, see . + * + * Copyright (C) 2026 Pau Aliagas + */ + +/* libFuzzer harness: THE REPLY STREAM (src/relay.c), which reads what each worker writes on its feedback socket, cuts + * it into NUL-terminated lines and writes them to the controller, ending the gestures a worker or an instance left + * held. The relay is included whole, as tests/gesture_alloc_test.c does, with its socket calls answered here: recv + * hands it the bytes the input chose, cut where the input chose, and send collects what the controller would read. + * No socket is opened and no thread is started: the harness calls drain(), relay_remove(), relay_instance_gone() and + * relay_event() where the relay's thread and the supervisor would. + * + * The relay is built with LINE_LIMIT at 6000 bytes, so an input of a few kilobytes reaches the drop of a long line, + * and the buffer's growth past its first 8192 bytes. + * + * Input: a source (worker feedback socket) is open from the start; then up to OPS_MAX operations, each [op] and its + * arguments, op % 10: + * 0 a worker more (at most SOURCES_MAX at once) + * 1 s n ... [s] [u16 LE n] n bytes: what source s writes next, handed to one recv or more, never two writes + * to one recv + * 2 s the relay reads source s (its thread's drain on POLLIN) + * 3 s source s's worker closes its end: recv answers 0 once its bytes are read + * 4 s the supervisor removes source s (relay_remove): the worker died or was stopped + * 5 id instance id is removed (relay_instance_gone) + * 6 s the next recv of source s is interrupted (EINTR) + * 7 n ... [u16 LE n] n bytes: one event of the daemon's own (relay_event), up to its first NUL + * 8 k the controller's socket takes at most k bytes a send from now on (0: all of them), and its next + * send is interrupted (EINTR) + * 9 m the controller goes away for the rest of the input: m & 1, it closes its connection + * (relay_controller_gone); else its next send fails, as on a connection it reset + * s is taken modulo the open sources. + * + * The reference: the controller should read, for each source, every complete line (its bytes up to a NUL) the worker + * wrote that is LINE_LIMIT bytes or shorter, once each, in order (PHD_RELAY VERBATIM and ORDER); for each gesture a + * relayed "param_gesture begin" opened and no relayed end closed, one + * "param_gesture end" when its source is removed or its instance is (PHD_EVENTS param_gesture). + * Once the controller is gone it reads nothing more, and the relay still reads every worker (PHD_RELAY NO_CONTROLLER). + * + * Properties, each an abort(): + * - bounded: after every operation each source's buffer holds at most LINE_LIMIT bytes of an unfinished line, and + * its capacity stays under 2 * (LINE_LIMIT + 4096). + * Mutant: fuzz/mutants/reply-bounded.patch, the overflow check against the capacity instead of LINE_LIMIT. + * - no-loss: after every read the lines relayed for a source are exactly the reference's complete lines, none lost, + * none twice, none out of order, however the stream was cut and wherever the NULs fell. + * Mutant: fuzz/mutants/reply-no-loss.patch, a line of exactly LINE_LIMIT bytes dropped. + * - shape: every line the relay writes of its own is a declared feedback line: an event as the daemon wrote it, or + * the end of a gesture still held, once for each such gesture and for no other; and the controller's stream + * always ends on a whole line. + * Mutant: fuzz/mutants/reply-shape.patch, a held gesture ended with begin. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "fuzz.h" + +static ssize_t fuzz_recv(int fd, void *buf, size_t len, int flags); +static ssize_t fuzz_send(int fd, const void *buf, size_t len, int flags); +static int fuzz_shutdown(int fd, int how); +static int fuzz_close(int fd); +static int fuzz_fcntl(int fd, int cmd, ...); + +#define recv fuzz_recv +#define send fuzz_send +#define shutdown fuzz_shutdown +#define close fuzz_close +#define fcntl fuzz_fcntl +#define LINE_LIMIT 6000 +#include "../src/relay.c" +#undef recv +#undef send +#undef shutdown +#undef close +#undef fcntl + +#define SOURCES_MAX 4 +#define OPS_MAX 512 +#define CLIENT_FD 3 +#define FIRST_FD 100 + +/* a line the controller should read, or a gesture held: a copy of its bytes */ +struct text { + char *s; + size_t n; +}; + +struct list { + struct text *v; + int n, cap; +}; + +static void list_push(struct list *l, const char *s, size_t n) +{ + if (l->n == l->cap) + { + int cap = l->cap ? l->cap * 2 : 8; + struct text *v = realloc(l->v, (size_t)cap * sizeof(*v)); + + if (!v) + abort(); + l->v = v; + l->cap = cap; + } + l->v[l->n].s = malloc(n + 1); + if (!l->v[l->n].s) + abort(); + memcpy(l->v[l->n].s, s, n); + l->v[l->n].s[n] = '\0'; + l->v[l->n].n = n; + l->n++; +} + +static void list_drop(struct list *l, int i) +{ + free(l->v[i].s); + memmove(&l->v[i], &l->v[i + 1], (size_t)(l->n - i - 1) * sizeof(l->v[0])); + l->n--; +} + +static void list_free(struct list *l) +{ + while (l->n) + list_drop(l, l->n - 1); + free(l->v); + l->v = NULL; + l->cap = 0; +} + +static int list_find(const struct list *l, const char *s, size_t n) +{ + int i; + + for (i = 0; i < l->n; i++) + if (l->v[i].n == n && !memcmp(l->v[i].s, s, n)) + return i; + return -1; +} + +/* one worker's feedback socket, as the harness writes it and as the reference reads it */ +struct source { + int fd; + int open; /* in the relay's table */ + int closed; /* the worker closed its end */ + int eintr; /* the next recv is interrupted */ + struct list pending; /* what the worker wrote and recv has not handed over, one entry per write */ + size_t pending_off; /* how much of the first entry recv has handed over */ + /* the reference splitter over the bytes recv handed over */ + char *partial; + size_t npartial; + int overlong; + struct list expected; /* complete lines the controller should read and has not */ + struct list held; /* gestures the relayed lines opened and did not end, each its line up to the last word */ +}; + +static struct source g_s[SOURCES_MAX]; +static int g_nfd; + +/* what the controller read during one operation */ +static char *g_out; +static size_t g_nout, g_capout; +/* how the controller's socket takes a send: at most g_send_max bytes (0: all), the next one interrupted, the next one + * failed; and whether the controller is gone */ +static size_t g_send_max; +static int g_send_eintr, g_send_dead, g_gone; + +static struct source *source_of(int fd) +{ + int i; + + for (i = 0; i < SOURCES_MAX; i++) + if (g_s[i].open && g_s[i].fd == fd) + return &g_s[i]; + return NULL; +} + +/* the reference splitter: a line ends at a NUL, and one longer than LINE_LIMIT is not a line of the protocol */ +static void reference_feed(struct source *s, const char *p, size_t n) +{ + size_t i; + + for (i = 0; i < n; i++) + { + if (p[i] == '\0') + { + if (!s->overlong) + list_push(&s->expected, s->partial, s->npartial); + s->npartial = 0; + s->overlong = 0; + } + else if (!s->overlong) + { + if (s->npartial == LINE_LIMIT) + s->overlong = 1; + else + s->partial[s->npartial++] = p[i]; + } + } +} + +static ssize_t fuzz_recv(int fd, void *buf, size_t len, int flags) +{ + struct source *s = source_of(fd); + size_t n; + + (void)flags; + if (s == NULL) + { + errno = EBADF; + return -1; + } + if (s->eintr) + { + s->eintr = 0; + errno = EINTR; + return -1; + } + if (s->pending.n == 0) + { + if (s->closed) + return 0; + errno = EAGAIN; + return -1; + } + n = s->pending.v[0].n - s->pending_off; + if (n > len) + n = len; + memcpy(buf, s->pending.v[0].s + s->pending_off, n); + reference_feed(s, s->pending.v[0].s + s->pending_off, n); + s->pending_off += n; + if (s->pending_off == s->pending.v[0].n) + { + list_drop(&s->pending, 0); + s->pending_off = 0; + } + return (ssize_t)n; +} + +static ssize_t fuzz_send(int fd, const void *buf, size_t len, int flags) +{ + (void)flags; + if (fd != CLIENT_FD) + abort(); + if (g_send_eintr) + { + g_send_eintr = 0; + errno = EINTR; + return -1; + } + if (g_send_dead) + { + errno = EPIPE; + return -1; + } + if (g_send_max && len > g_send_max) + len = g_send_max; + if (g_nout + len > g_capout) + { + size_t cap = (g_nout + len) * 2; + char *out = realloc(g_out, cap); + + if (!out) + abort(); + g_out = out; + g_capout = cap; + } + memcpy(g_out + g_nout, buf, len); + g_nout += len; + return (ssize_t)len; +} + +static int fuzz_shutdown(int fd, int how) +{ + (void)fd; + (void)how; + return 0; +} + +static int fuzz_close(int fd) +{ + (void)fd; + return 0; +} + +static int fuzz_fcntl(int fd, int cmd, ...) +{ + (void)fd; + (void)cmd; + return 0; +} + +/* ---------------------------------------------------------------- the properties */ + +/* the bounded property, over every source in the relay's table */ +__attribute__((noinline)) static void check_bounded(void) +{ + int i; + + for (i = 0; i < g_nsrc; i++) + { + /* an unfinished line the relay keeps is never past LINE_LIMIT, and what holds it does not grow without end */ + if (g_src[i].used > LINE_LIMIT || g_src[i].used > g_src[i].cap) + abort(); + if (g_src[i].cap >= 2 * ((size_t)LINE_LIMIT + 4096)) + abort(); + } +} + +/* the controller's stream of one operation, cut into lines: *at is where the next one starts */ +static int next_line(size_t *at, const char **line, size_t *len) +{ + const char *nul; + + if (*at == g_nout) + return 0; + nul = memchr(g_out + *at, '\0', g_nout - *at); + /* the shape property: the stream ends on a whole line */ + if (nul == NULL) + abort(); + *line = g_out + *at; + *len = (size_t)(nul - *line); + *at += *len + 1; + return 1; +} + +/* a relayed line that opens or ends a gesture, read as PHD_EVENTS declares param_gesture */ +static void gesture_model(struct source *s, const char *line, size_t len) +{ + size_t verb = strlen(PHD_EVENT_PARAM_GESTURE), last; + const char *word; + + if (len <= verb || memcmp(line, PHD_EVENT_PARAM_GESTURE, verb) || line[verb] != ' ') + return; + for (last = len; last > 0 && line[last - 1] != ' '; last--) + ; + if (last == 0 || last - 1 == verb) + return; + word = line + last; + if (len - last == strlen(PHD_GESTURE_BEGIN) && !memcmp(word, PHD_GESTURE_BEGIN, len - last)) + { + if (list_find(&s->held, line, last - 1) < 0) + list_push(&s->held, line, last - 1); + } + else if (len - last == strlen(PHD_GESTURE_END) && !memcmp(word, PHD_GESTURE_END, len - last)) + { + int i = list_find(&s->held, line, last - 1); + + if (i >= 0) + list_drop(&s->held, i); + } +} + +/* the no-loss property for one source: the next `n` lines of the stream are its expected lines, in order, and then + * none is expected any more */ +__attribute__((noinline)) static void check_no_loss(struct source *s, size_t *at) +{ + const char *line; + size_t len; + + while (s->expected.n > 0) + { + if (g_gone) + { + /* read and dropped: its gestures are still followed */ + gesture_model(s, s->expected.v[0].s, s->expected.v[0].n); + list_drop(&s->expected, 0); + continue; + } + /* every complete line it wrote is read by the controller, as it wrote it */ + if (!next_line(at, &line, &len)) + abort(); + if (len != s->expected.v[0].n || memcmp(line, s->expected.v[0].s, len)) + abort(); + gesture_model(s, line, len); + list_drop(&s->expected, 0); + } +} + +static int has_prefix(const char *s, size_t n, const char *prefix) +{ + size_t p = strlen(prefix); + + return n >= p && !memcmp(s, prefix, p); +} + +/* the shape property for the gesture ends the relay writes for a source: one " end" for each held gesture + * `prefix` selects, and no other line */ +__attribute__((noinline)) static void check_shape_ends(struct source *s, const char *prefix, size_t *at) +{ + const char *line; + size_t len, end = strlen(" " PHD_GESTURE_END); + int want = 0, i; + + for (i = 0; i < s->held.n; i++) + if (has_prefix(s->held.v[i].s, s->held.v[i].n, prefix)) + want++; + if (g_gone) + { + /* ended where no controller reads them */ + for (i = s->held.n - 1; i >= 0; i--) + if (has_prefix(s->held.v[i].s, s->held.v[i].n, prefix)) + list_drop(&s->held, i); + return; + } + while (want-- > 0) + { + /* the end of a gesture still held, and of each one only once */ + if (!next_line(at, &line, &len)) + abort(); + if (len < end || memcmp(line + len - end, " " PHD_GESTURE_END, end)) + abort(); + i = list_find(&s->held, line, len - end); + if (i < 0 || !has_prefix(line, len, prefix)) + abort(); + list_drop(&s->held, i); + } +} + +/* the whole stream of an operation was read: nothing is left over */ +__attribute__((noinline)) static void check_shape_done(size_t at) +{ + /* no line the reference does not account for */ + if (at != g_nout) + abort(); +} + +/* the shape property for an event of the daemon's own: the controller reads it as it was written */ +__attribute__((noinline)) static void check_shape_event(const char *event) +{ + size_t at = 0, len; + const char *line; + + if (g_gone) + { + check_shape_done(at); + return; + } + if (!next_line(&at, &line, &len)) + abort(); + if (len != strlen(event) || memcmp(line, event, len)) + abort(); + check_shape_done(at); +} + +/* ---------------------------------------------------------------- the operations */ + +static void source_reset(struct source *s) +{ + list_free(&s->pending); + list_free(&s->expected); + list_free(&s->held); + free(s->partial); + memset(s, 0, sizeof(*s)); +} + +static int open_count(void) +{ + int i, n = 0; + + for (i = 0; i < SOURCES_MAX; i++) + n += g_s[i].open; + return n; +} + +/* the k-th open source, k taken modulo the open ones */ +static struct source *pick(uint8_t k) +{ + int n = open_count(), i; + + if (n == 0) + return NULL; + k %= n; + for (i = 0; i < SOURCES_MAX; i++) + if (g_s[i].open && k-- == 0) + return &g_s[i]; + return NULL; +} + +static source_t *relay_source(int fd) +{ + int i; + + for (i = 0; i < g_nsrc; i++) + if (g_src[i].fd == fd) + return &g_src[i]; + return NULL; +} + +static void op_add(void) +{ + int i; + + for (i = 0; i < SOURCES_MAX; i++) + if (!g_s[i].open) + { + source_reset(&g_s[i]); + g_s[i].fd = FIRST_FD + g_nfd++; + g_s[i].partial = malloc(LINE_LIMIT); + if (!g_s[i].partial) + abort(); + g_s[i].open = 1; + if (relay_add(g_s[i].fd) != 0) + abort(); + return; + } +} + +static void op_drain(struct source *s) +{ + source_t *r = relay_source(s->fd); + size_t at = 0; + + if (r == NULL || r->eof) + return; + pthread_mutex_lock(&g_lock); + drain(r); + pthread_mutex_unlock(&g_lock); + check_no_loss(s, &at); + check_shape_done(at); +} + +static void op_remove(struct source *s) +{ + size_t at = 0; + + relay_remove(s->fd); + check_no_loss(s, &at); + check_shape_ends(s, "", &at); + check_shape_done(at); + source_reset(s); +} + +static void op_gone(int id) +{ + char prefix[48]; + size_t at = 0; + int i, k; + + snprintf(prefix, sizeof(prefix), "%s %d ", PHD_EVENT_PARAM_GESTURE, id); + relay_instance_gone(id); + /* the relay walks its table in order: each source's lines, then the ends of its gestures of that instance */ + for (i = 0; i < g_nsrc; i++) + for (k = 0; k < SOURCES_MAX; k++) + if (g_s[k].open && g_s[k].fd == g_src[i].fd) + { + check_no_loss(&g_s[k], &at); + check_shape_ends(&g_s[k], prefix, &at); + } + check_shape_done(at); +} + +static void op_event(const uint8_t *p, size_t n) +{ + char *event = malloc(n + 1); + + if (!event) + abort(); + memcpy(event, p, n); + event[n] = '\0'; + relay_event(event); + check_shape_event(event); + free(event); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + struct fuzz_in in; + int ops, i; + + if (size >= FUZZ_INPUT_MAX) + return 0; + in = fuzz_in(data, size); + g_client = CLIENT_FD; + g_client_broken = 0; + g_nfd = 0; + g_send_max = 0; + g_send_eintr = g_send_dead = g_gone = 0; + op_add(); + for (ops = 0; ops < OPS_MAX && in.n; ops++) + { + uint8_t op = fuzz_u8(&in) % 10; + struct source *s = NULL; + const uint8_t *p; + size_t n; + + g_nout = 0; + if (op == 1 || op == 2 || op == 3 || op == 4 || op == 6) + { + s = pick(fuzz_u8(&in)); + if (s == NULL && op != 1) + continue; + } + switch (op) + { + case 0: + op_add(); + break; + case 1: + n = fuzz_take(&in, fuzz_u16(&in), &p); + if (s && n) + list_push(&s->pending, (const char *)p, n); + break; + case 2: + op_drain(s); + break; + case 3: + s->closed = 1; + break; + case 4: + op_remove(s); + break; + case 5: + op_gone(fuzz_u8(&in)); + break; + case 6: + s->eintr = 1; + break; + case 7: + n = fuzz_take(&in, fuzz_u16(&in), &p); + op_event(p, strnlen((const char *)p, n)); + break; + case 8: + g_send_max = fuzz_u8(&in); + g_send_eintr = 1; + break; + default: + if (fuzz_u8(&in) & 1) + relay_controller_gone(); + else + g_send_dead = 1; + g_gone = 1; + break; + } + check_bounded(); + } + /* the daemon stops: every source left is closed and every gesture still held is ended, in the relay's table order */ + { + int order[SOURCES_MAX], norder = 0, k; + size_t at = 0; + + for (i = 0; i < g_nsrc && norder < SOURCES_MAX; i++) + order[norder++] = g_src[i].fd; + g_nout = 0; + relay_finish(); + for (i = 0; i < norder; i++) + for (k = 0; k < SOURCES_MAX; k++) + if (g_s[k].open && g_s[k].fd == order[i]) + check_shape_ends(&g_s[k], "", &at); + check_shape_done(at); + } + for (i = 0; i < SOURCES_MAX; i++) + source_reset(&g_s[i]); + return 0; +} diff --git a/src/relay.c b/src/relay.c index b0bddfd..ff852de 100644 --- a/src/relay.c +++ b/src/relay.c @@ -34,8 +34,11 @@ #include "relay.h" -/* a worker line longer than this, with no NUL in it, is not a line of the protocol: it is dropped */ +/* a worker line longer than this, with no NUL in it, is not a line of the protocol: it is dropped; the fuzz harness + * (fuzz/fuzz_reply.c) builds the relay with a smaller one, so its inputs reach the drop */ +#ifndef LINE_LIMIT #define LINE_LIMIT (1024 * 1024) +#endif typedef struct SOURCE_T { int fd; From 863a0b0d4142b5ffb857e4818ea3a0e5616ed5bc Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 21:48:40 +0200 Subject: [PATCH 05/17] Seed both harnesses from the declared protocol fuzz/gen_corpus.py writes the seed corpus from protocol/plugin-hostd.json and protocol/mod-host.json, every verb with sample arguments, with its optional words, with one word too many, refused by the supervisor and split by tabs, and from the send lines of README.md and docs/own-host.md. The reply seeds cut worker feedback at every byte and before every NUL, hold and end gestures, and put lines at and past the line limit. --- fuzz/corpus/command/add-default | Bin 0 -> 32 bytes fuzz/corpus/command/add-fuzz_client | Bin 0 -> 36 bytes .../command/add-instance-past-the-table | Bin 0 -> 28 bytes fuzz/corpus/command/add-own | Bin 0 -> 28 bytes fuzz/corpus/command/add-own-fuzz_client-extra | Bin 0 -> 46 bytes fuzz/corpus/command/add-pool-p | Bin 0 -> 31 bytes fuzz/corpus/command/add-pool-p-fuzz_client | Bin 0 -> 43 bytes fuzz/corpus/command/add-too-few | Bin 0 -> 22 bytes fuzz/corpus/command/chain-id-not-a-number | Bin 0 -> 12 bytes fuzz/corpus/command/chain-not-a-number | Bin 0 -> 14 bytes fuzz/corpus/command/chain-of-many | Bin 0 -> 210 bytes fuzz/corpus/command/empty | Bin 0 -> 3 bytes fuzz/corpus/command/leading-space | Bin 0 -> 23 bytes fuzz/corpus/command/many-words | Bin 0 -> 324 bytes fuzz/corpus/command/mod-host-add | Bin 0 -> 10 bytes fuzz/corpus/command/mod-host-add-extra | Bin 0 -> 21 bytes fuzz/corpus/command/mod-host-add-full | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-add-refused | Bin 0 -> 10 bytes fuzz/corpus/command/mod-host-add-tabs | Bin 0 -> 12 bytes fuzz/corpus/command/mod-host-bundle_add | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-bundle_add-extra | Bin 0 -> 21 bytes .../command/mod-host-bundle_add-refused | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-bundle_add-tabs | Bin 0 -> 17 bytes fuzz/corpus/command/mod-host-bundle_remove | Bin 0 -> 20 bytes .../command/mod-host-bundle_remove-extra | Bin 0 -> 26 bytes .../command/mod-host-bundle_remove-refused | Bin 0 -> 20 bytes .../command/mod-host-bundle_remove-tabs | Bin 0 -> 22 bytes fuzz/corpus/command/mod-host-bypass | Bin 0 -> 13 bytes fuzz/corpus/command/mod-host-bypass-extra | Bin 0 -> 19 bytes fuzz/corpus/command/mod-host-bypass-refused | Bin 0 -> 13 bytes fuzz/corpus/command/mod-host-bypass-tabs | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-cc_map | Bin 0 -> 39 bytes fuzz/corpus/command/mod-host-cc_map-extra | Bin 0 -> 50 bytes fuzz/corpus/command/mod-host-cc_map-full | Bin 0 -> 44 bytes fuzz/corpus/command/mod-host-cc_map-refused | Bin 0 -> 39 bytes fuzz/corpus/command/mod-host-cc_map-tabs | Bin 0 -> 41 bytes fuzz/corpus/command/mod-host-cc_unmap | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-cc_unmap-extra | Bin 0 -> 21 bytes fuzz/corpus/command/mod-host-cc_unmap-refused | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-cc_unmap-tabs | Bin 0 -> 17 bytes fuzz/corpus/command/mod-host-cc_value_set | Bin 0 -> 23 bytes .../command/mod-host-cc_value_set-extra | Bin 0 -> 29 bytes .../command/mod-host-cc_value_set-refused | Bin 0 -> 23 bytes .../corpus/command/mod-host-cc_value_set-tabs | Bin 0 -> 25 bytes fuzz/corpus/command/mod-host-connect | Bin 0 -> 14 bytes fuzz/corpus/command/mod-host-connect-extra | Bin 0 -> 20 bytes fuzz/corpus/command/mod-host-connect-refused | Bin 0 -> 14 bytes fuzz/corpus/command/mod-host-connect-tabs | Bin 0 -> 16 bytes fuzz/corpus/command/mod-host-cpu_load | Bin 0 -> 11 bytes fuzz/corpus/command/mod-host-cpu_load-extra | Bin 0 -> 17 bytes fuzz/corpus/command/mod-host-cpu_load-refused | Bin 0 -> 11 bytes fuzz/corpus/command/mod-host-cpu_load-tabs | Bin 0 -> 13 bytes fuzz/corpus/command/mod-host-cv_map | Bin 0 -> 25 bytes fuzz/corpus/command/mod-host-cv_map-extra | Bin 0 -> 31 bytes fuzz/corpus/command/mod-host-cv_map-refused | Bin 0 -> 25 bytes fuzz/corpus/command/mod-host-cv_map-tabs | Bin 0 -> 27 bytes fuzz/corpus/command/mod-host-cv_unmap | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-cv_unmap-extra | Bin 0 -> 21 bytes fuzz/corpus/command/mod-host-cv_unmap-refused | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-cv_unmap-tabs | Bin 0 -> 17 bytes fuzz/corpus/command/mod-host-disconnect | Bin 0 -> 17 bytes fuzz/corpus/command/mod-host-disconnect-extra | Bin 0 -> 23 bytes .../command/mod-host-disconnect-refused | Bin 0 -> 17 bytes fuzz/corpus/command/mod-host-disconnect-tabs | Bin 0 -> 19 bytes fuzz/corpus/command/mod-host-feature_enable | Bin 0 -> 21 bytes .../command/mod-host-feature_enable-extra | Bin 0 -> 27 bytes .../command/mod-host-feature_enable-refused | Bin 0 -> 21 bytes .../command/mod-host-feature_enable-tabs | Bin 0 -> 23 bytes fuzz/corpus/command/mod-host-help | Bin 0 -> 7 bytes fuzz/corpus/command/mod-host-help-extra | Bin 0 -> 13 bytes fuzz/corpus/command/mod-host-help-refused | Bin 0 -> 7 bytes fuzz/corpus/command/mod-host-help-tabs | Bin 0 -> 9 bytes fuzz/corpus/command/mod-host-hmi_map | Bin 0 -> 36 bytes fuzz/corpus/command/mod-host-hmi_map-extra | Bin 0 -> 42 bytes fuzz/corpus/command/mod-host-hmi_map-refused | Bin 0 -> 36 bytes fuzz/corpus/command/mod-host-hmi_map-tabs | Bin 0 -> 38 bytes fuzz/corpus/command/mod-host-hmi_unmap | Bin 0 -> 16 bytes fuzz/corpus/command/mod-host-hmi_unmap-extra | Bin 0 -> 22 bytes .../corpus/command/mod-host-hmi_unmap-refused | Bin 0 -> 16 bytes fuzz/corpus/command/mod-host-hmi_unmap-tabs | Bin 0 -> 18 bytes fuzz/corpus/command/mod-host-licensee | Bin 0 -> 13 bytes fuzz/corpus/command/mod-host-licensee-extra | Bin 0 -> 19 bytes fuzz/corpus/command/mod-host-licensee-refused | Bin 0 -> 13 bytes fuzz/corpus/command/mod-host-licensee-tabs | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-load | Bin 0 -> 9 bytes fuzz/corpus/command/mod-host-load-extra | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-load-refused | Bin 0 -> 9 bytes fuzz/corpus/command/mod-host-load-tabs | Bin 0 -> 11 bytes fuzz/corpus/command/mod-host-midi_learn | Bin 0 -> 25 bytes fuzz/corpus/command/mod-host-midi_learn-extra | Bin 0 -> 31 bytes .../command/mod-host-midi_learn-refused | Bin 0 -> 25 bytes fuzz/corpus/command/mod-host-midi_learn-tabs | Bin 0 -> 27 bytes fuzz/corpus/command/mod-host-midi_map | Bin 0 -> 27 bytes fuzz/corpus/command/mod-host-midi_map-extra | Bin 0 -> 33 bytes fuzz/corpus/command/mod-host-midi_map-refused | Bin 0 -> 27 bytes fuzz/corpus/command/mod-host-midi_map-tabs | Bin 0 -> 29 bytes fuzz/corpus/command/mod-host-midi_unmap | Bin 0 -> 17 bytes fuzz/corpus/command/mod-host-midi_unmap-extra | Bin 0 -> 23 bytes .../command/mod-host-midi_unmap-refused | Bin 0 -> 17 bytes fuzz/corpus/command/mod-host-midi_unmap-tabs | Bin 0 -> 19 bytes fuzz/corpus/command/mod-host-monitor | Bin 0 -> 16 bytes fuzz/corpus/command/mod-host-monitor-extra | Bin 0 -> 22 bytes fuzz/corpus/command/mod-host-monitor-refused | Bin 0 -> 16 bytes fuzz/corpus/command/mod-host-monitor-tabs | Bin 0 -> 18 bytes .../command/mod-host-monitor_midi_program | Bin 0 -> 27 bytes .../mod-host-monitor_midi_program-extra | Bin 0 -> 33 bytes .../mod-host-monitor_midi_program-refused | Bin 0 -> 27 bytes .../mod-host-monitor_midi_program-tabs | Bin 0 -> 29 bytes fuzz/corpus/command/mod-host-monitor_output | Bin 0 -> 21 bytes .../command/mod-host-monitor_output-extra | Bin 0 -> 27 bytes .../command/mod-host-monitor_output-refused | Bin 0 -> 21 bytes .../command/mod-host-monitor_output-tabs | Bin 0 -> 23 bytes .../corpus/command/mod-host-output_data_ready | Bin 0 -> 20 bytes .../command/mod-host-output_data_ready-extra | Bin 0 -> 26 bytes .../mod-host-output_data_ready-refused | Bin 0 -> 20 bytes .../command/mod-host-output_data_ready-tabs | Bin 0 -> 22 bytes fuzz/corpus/command/mod-host-param_get | Bin 0 -> 16 bytes fuzz/corpus/command/mod-host-param_get-extra | Bin 0 -> 22 bytes .../corpus/command/mod-host-param_get-refused | Bin 0 -> 16 bytes fuzz/corpus/command/mod-host-param_get-tabs | Bin 0 -> 18 bytes fuzz/corpus/command/mod-host-param_info | Bin 0 -> 17 bytes fuzz/corpus/command/mod-host-param_info-extra | Bin 0 -> 23 bytes .../command/mod-host-param_info-refused | Bin 0 -> 17 bytes fuzz/corpus/command/mod-host-param_info-tabs | Bin 0 -> 19 bytes fuzz/corpus/command/mod-host-param_monitor | Bin 0 -> 26 bytes .../command/mod-host-param_monitor-extra | Bin 0 -> 32 bytes .../command/mod-host-param_monitor-refused | Bin 0 -> 26 bytes .../command/mod-host-param_monitor-tabs | Bin 0 -> 28 bytes fuzz/corpus/command/mod-host-param_set | Bin 0 -> 18 bytes fuzz/corpus/command/mod-host-param_set-extra | Bin 0 -> 24 bytes .../corpus/command/mod-host-param_set-refused | Bin 0 -> 18 bytes fuzz/corpus/command/mod-host-param_set-tabs | Bin 0 -> 20 bytes fuzz/corpus/command/mod-host-patch_get | Bin 0 -> 16 bytes fuzz/corpus/command/mod-host-patch_get-extra | Bin 0 -> 22 bytes .../corpus/command/mod-host-patch_get-refused | Bin 0 -> 16 bytes fuzz/corpus/command/mod-host-patch_get-tabs | Bin 0 -> 18 bytes fuzz/corpus/command/mod-host-patch_set | Bin 0 -> 18 bytes fuzz/corpus/command/mod-host-patch_set-extra | Bin 0 -> 24 bytes .../corpus/command/mod-host-patch_set-refused | Bin 0 -> 18 bytes fuzz/corpus/command/mod-host-patch_set-tabs | Bin 0 -> 20 bytes fuzz/corpus/command/mod-host-preset_load | Bin 0 -> 18 bytes .../corpus/command/mod-host-preset_load-extra | Bin 0 -> 24 bytes .../command/mod-host-preset_load-refused | Bin 0 -> 18 bytes fuzz/corpus/command/mod-host-preset_load-tabs | Bin 0 -> 20 bytes fuzz/corpus/command/mod-host-preset_save | Bin 0 -> 22 bytes .../corpus/command/mod-host-preset_save-extra | Bin 0 -> 28 bytes .../command/mod-host-preset_save-refused | Bin 0 -> 22 bytes fuzz/corpus/command/mod-host-preset_save-tabs | Bin 0 -> 24 bytes fuzz/corpus/command/mod-host-preset_show | Bin 0 -> 16 bytes .../corpus/command/mod-host-preset_show-extra | Bin 0 -> 22 bytes .../command/mod-host-preset_show-refused | Bin 0 -> 16 bytes fuzz/corpus/command/mod-host-preset_show-tabs | Bin 0 -> 18 bytes fuzz/corpus/command/mod-host-quit | Bin 0 -> 7 bytes fuzz/corpus/command/mod-host-quit-extra | Bin 0 -> 13 bytes fuzz/corpus/command/mod-host-quit-refused | Bin 0 -> 7 bytes fuzz/corpus/command/mod-host-quit-tabs | Bin 0 -> 9 bytes fuzz/corpus/command/mod-host-remote_page_get | Bin 0 -> 22 bytes .../command/mod-host-remote_page_get-extra | Bin 0 -> 28 bytes .../command/mod-host-remote_page_get-refused | Bin 0 -> 22 bytes .../command/mod-host-remote_page_get-tabs | Bin 0 -> 24 bytes fuzz/corpus/command/mod-host-remote_pages | Bin 0 -> 17 bytes .../command/mod-host-remote_pages-extra | Bin 0 -> 23 bytes .../command/mod-host-remote_pages-refused | Bin 0 -> 17 bytes .../corpus/command/mod-host-remote_pages-tabs | Bin 0 -> 19 bytes fuzz/corpus/command/mod-host-remove | Bin 0 -> 11 bytes fuzz/corpus/command/mod-host-remove-extra | Bin 0 -> 17 bytes fuzz/corpus/command/mod-host-remove-refused | Bin 0 -> 11 bytes fuzz/corpus/command/mod-host-remove-tabs | Bin 0 -> 13 bytes fuzz/corpus/command/mod-host-save | Bin 0 -> 9 bytes fuzz/corpus/command/mod-host-save-extra | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-save-refused | Bin 0 -> 9 bytes fuzz/corpus/command/mod-host-save-tabs | Bin 0 -> 11 bytes fuzz/corpus/command/mod-host-set_bpb | Bin 0 -> 14 bytes fuzz/corpus/command/mod-host-set_bpb-extra | Bin 0 -> 20 bytes fuzz/corpus/command/mod-host-set_bpb-refused | Bin 0 -> 14 bytes fuzz/corpus/command/mod-host-set_bpb-tabs | Bin 0 -> 16 bytes fuzz/corpus/command/mod-host-set_bpm | Bin 0 -> 14 bytes fuzz/corpus/command/mod-host-set_bpm-extra | Bin 0 -> 20 bytes fuzz/corpus/command/mod-host-set_bpm-refused | Bin 0 -> 14 bytes fuzz/corpus/command/mod-host-set_bpm-tabs | Bin 0 -> 16 bytes fuzz/corpus/command/mod-host-show_external_ui | Bin 0 -> 21 bytes .../command/mod-host-show_external_ui-extra | Bin 0 -> 27 bytes .../command/mod-host-show_external_ui-refused | Bin 0 -> 21 bytes .../command/mod-host-show_external_ui-tabs | Bin 0 -> 23 bytes fuzz/corpus/command/mod-host-state_load | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-state_load-extra | Bin 0 -> 21 bytes .../command/mod-host-state_load-refused | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-state_load-tabs | Bin 0 -> 17 bytes fuzz/corpus/command/mod-host-state_save | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-state_save-extra | Bin 0 -> 21 bytes .../command/mod-host-state_save-refused | Bin 0 -> 15 bytes fuzz/corpus/command/mod-host-state_save-tabs | Bin 0 -> 17 bytes fuzz/corpus/command/mod-host-state_tmpdir | Bin 0 -> 17 bytes .../command/mod-host-state_tmpdir-extra | Bin 0 -> 23 bytes .../command/mod-host-state_tmpdir-refused | Bin 0 -> 17 bytes .../corpus/command/mod-host-state_tmpdir-tabs | Bin 0 -> 19 bytes fuzz/corpus/command/mod-host-track_info | Bin 0 -> 19 bytes fuzz/corpus/command/mod-host-track_info-extra | Bin 0 -> 30 bytes fuzz/corpus/command/mod-host-track_info-full | Bin 0 -> 24 bytes .../command/mod-host-track_info-refused | Bin 0 -> 19 bytes fuzz/corpus/command/mod-host-track_info-tabs | Bin 0 -> 21 bytes fuzz/corpus/command/mod-host-transport | Bin 0 -> 22 bytes fuzz/corpus/command/mod-host-transport-extra | Bin 0 -> 28 bytes .../corpus/command/mod-host-transport-refused | Bin 0 -> 22 bytes fuzz/corpus/command/mod-host-transport-tabs | Bin 0 -> 24 bytes fuzz/corpus/command/mod-host-transport_sync | Bin 0 -> 19 bytes .../command/mod-host-transport_sync-extra | Bin 0 -> 25 bytes .../command/mod-host-transport_sync-refused | Bin 0 -> 19 bytes .../command/mod-host-transport_sync-tabs | Bin 0 -> 21 bytes fuzz/corpus/command/nul-inside | Bin 0 -> 20 bytes fuzz/corpus/command/only-spaces | Bin 0 -> 7 bytes fuzz/corpus/command/own-host-1 | Bin 0 -> 60 bytes fuzz/corpus/command/own-host-2 | Bin 0 -> 18 bytes fuzz/corpus/command/own-host-3 | Bin 0 -> 16 bytes fuzz/corpus/command/own-host-4 | Bin 0 -> 7 bytes fuzz/corpus/command/own-host-5 | Bin 0 -> 31 bytes fuzz/corpus/command/own-host-6 | Bin 0 -> 22 bytes fuzz/corpus/command/own-host-7 | Bin 0 -> 20 bytes fuzz/corpus/command/own-host-8 | Bin 0 -> 14 bytes fuzz/corpus/command/own-host-session | Bin 0 -> 188 bytes fuzz/corpus/command/pin-clear-refused | Bin 0 -> 16 bytes fuzz/corpus/command/pin-set-bad-verdict-key | Bin 0 -> 190 bytes fuzz/corpus/command/pin-set-layout-unknown | Bin 0 -> 105 bytes fuzz/corpus/command/pin-set-long | Bin 0 -> 4281 bytes fuzz/corpus/command/plugin-hostd-add | Bin 0 -> 24 bytes fuzz/corpus/command/plugin-hostd-add-extra | Bin 0 -> 46 bytes fuzz/corpus/command/plugin-hostd-add-full | Bin 0 -> 40 bytes fuzz/corpus/command/plugin-hostd-add-refused | Bin 0 -> 24 bytes fuzz/corpus/command/plugin-hostd-add-tabs | Bin 0 -> 26 bytes .../corpus/command/plugin-hostd-all_notes_off | Bin 0 -> 18 bytes .../command/plugin-hostd-all_notes_off-extra | Bin 0 -> 24 bytes .../plugin-hostd-all_notes_off-refused | Bin 0 -> 18 bytes .../command/plugin-hostd-all_notes_off-tabs | Bin 0 -> 20 bytes fuzz/corpus/command/plugin-hostd-chain | Bin 0 -> 12 bytes fuzz/corpus/command/plugin-hostd-chain-extra | Bin 0 -> 20 bytes fuzz/corpus/command/plugin-hostd-chain-full | Bin 0 -> 14 bytes .../corpus/command/plugin-hostd-chain-refused | Bin 0 -> 12 bytes fuzz/corpus/command/plugin-hostd-chain-tabs | Bin 0 -> 14 bytes fuzz/corpus/command/plugin-hostd-chain_clear | Bin 0 -> 16 bytes .../command/plugin-hostd-chain_clear-extra | Bin 0 -> 22 bytes .../command/plugin-hostd-chain_clear-refused | Bin 0 -> 16 bytes .../command/plugin-hostd-chain_clear-tabs | Bin 0 -> 18 bytes .../command/plugin-hostd-descriptor_get | Bin 0 -> 33 bytes .../command/plugin-hostd-descriptor_get-extra | Bin 0 -> 39 bytes .../plugin-hostd-descriptor_get-refused | Bin 0 -> 33 bytes .../command/plugin-hostd-descriptor_get-tabs | Bin 0 -> 35 bytes .../corpus/command/plugin-hostd-instance_info | Bin 0 -> 18 bytes .../command/plugin-hostd-instance_info-extra | Bin 0 -> 24 bytes .../plugin-hostd-instance_info-refused | Bin 0 -> 18 bytes .../command/plugin-hostd-instance_info-tabs | Bin 0 -> 20 bytes .../command/plugin-hostd-lv2_worker_set | Bin 0 -> 42 bytes .../command/plugin-hostd-lv2_worker_set-extra | Bin 0 -> 48 bytes .../plugin-hostd-lv2_worker_set-refused | Bin 0 -> 42 bytes .../command/plugin-hostd-lv2_worker_set-tabs | Bin 0 -> 44 bytes fuzz/corpus/command/plugin-hostd-note_input | Bin 0 -> 15 bytes .../command/plugin-hostd-note_input-extra | Bin 0 -> 21 bytes .../command/plugin-hostd-note_input-refused | Bin 0 -> 15 bytes .../command/plugin-hostd-note_input-tabs | Bin 0 -> 17 bytes fuzz/corpus/command/plugin-hostd-note_off | Bin 0 -> 22 bytes .../command/plugin-hostd-note_off-extra | Bin 0 -> 28 bytes .../command/plugin-hostd-note_off-refused | Bin 0 -> 22 bytes .../corpus/command/plugin-hostd-note_off-tabs | Bin 0 -> 24 bytes fuzz/corpus/command/plugin-hostd-note_on | Bin 0 -> 21 bytes .../corpus/command/plugin-hostd-note_on-extra | Bin 0 -> 27 bytes .../command/plugin-hostd-note_on-refused | Bin 0 -> 21 bytes fuzz/corpus/command/plugin-hostd-note_on-tabs | Bin 0 -> 23 bytes fuzz/corpus/command/plugin-hostd-param_info | Bin 0 -> 20 bytes .../command/plugin-hostd-param_info-extra | Bin 0 -> 26 bytes .../command/plugin-hostd-param_info-refused | Bin 0 -> 20 bytes .../command/plugin-hostd-param_info-tabs | Bin 0 -> 22 bytes fuzz/corpus/command/plugin-hostd-pin_accept | Bin 0 -> 29 bytes .../command/plugin-hostd-pin_accept-extra | Bin 0 -> 35 bytes .../command/plugin-hostd-pin_accept-refused | Bin 0 -> 29 bytes .../command/plugin-hostd-pin_accept-tabs | Bin 0 -> 31 bytes fuzz/corpus/command/plugin-hostd-pin_clear | Bin 0 -> 28 bytes .../command/plugin-hostd-pin_clear-extra | Bin 0 -> 34 bytes .../command/plugin-hostd-pin_clear-refused | Bin 0 -> 28 bytes .../command/plugin-hostd-pin_clear-tabs | Bin 0 -> 30 bytes fuzz/corpus/command/plugin-hostd-pin_expect | Bin 0 -> 93 bytes .../command/plugin-hostd-pin_expect-extra | Bin 0 -> 99 bytes .../command/plugin-hostd-pin_expect-refused | Bin 0 -> 93 bytes .../command/plugin-hostd-pin_expect-tabs | Bin 0 -> 95 bytes fuzz/corpus/command/plugin-hostd-pin_layout | Bin 0 -> 15 bytes .../command/plugin-hostd-pin_layout-extra | Bin 0 -> 21 bytes .../command/plugin-hostd-pin_layout-refused | Bin 0 -> 15 bytes .../command/plugin-hostd-pin_layout-tabs | Bin 0 -> 17 bytes fuzz/corpus/command/plugin-hostd-pin_set | Bin 0 -> 181 bytes .../corpus/command/plugin-hostd-pin_set-extra | Bin 0 -> 281 bytes fuzz/corpus/command/plugin-hostd-pin_set-full | Bin 0 -> 275 bytes .../command/plugin-hostd-pin_set-refused | Bin 0 -> 181 bytes fuzz/corpus/command/plugin-hostd-pin_set-tabs | Bin 0 -> 183 bytes fuzz/corpus/command/plugin-hostd-policy_set | Bin 0 -> 22 bytes .../command/plugin-hostd-policy_set-extra | Bin 0 -> 28 bytes .../command/plugin-hostd-policy_set-refused | Bin 0 -> 22 bytes .../command/plugin-hostd-policy_set-tabs | Bin 0 -> 24 bytes fuzz/corpus/command/plugin-hostd-pool_config | Bin 0 -> 18 bytes .../command/plugin-hostd-pool_config-extra | Bin 0 -> 24 bytes .../command/plugin-hostd-pool_config-refused | Bin 0 -> 18 bytes .../command/plugin-hostd-pool_config-tabs | Bin 0 -> 20 bytes fuzz/corpus/command/plugin-hostd-preset_get | Bin 0 -> 17 bytes .../command/plugin-hostd-preset_get-extra | Bin 0 -> 23 bytes .../command/plugin-hostd-preset_get-refused | Bin 0 -> 17 bytes .../command/plugin-hostd-preset_get-tabs | Bin 0 -> 19 bytes fuzz/corpus/command/plugin-hostd-preset_list | Bin 0 -> 16 bytes .../command/plugin-hostd-preset_list-extra | Bin 0 -> 22 bytes .../command/plugin-hostd-preset_list-refused | Bin 0 -> 16 bytes .../command/plugin-hostd-preset_list-tabs | Bin 0 -> 18 bytes .../command/plugin-hostd-quarantine_clear | Bin 0 -> 21 bytes .../plugin-hostd-quarantine_clear-extra | Bin 0 -> 27 bytes .../plugin-hostd-quarantine_clear-refused | Bin 0 -> 21 bytes .../plugin-hostd-quarantine_clear-tabs | Bin 0 -> 23 bytes .../command/plugin-hostd-remote_page_get | Bin 0 -> 22 bytes .../plugin-hostd-remote_page_get-extra | Bin 0 -> 28 bytes .../plugin-hostd-remote_page_get-refused | Bin 0 -> 22 bytes .../command/plugin-hostd-remote_page_get-tabs | Bin 0 -> 24 bytes fuzz/corpus/command/plugin-hostd-remote_pages | Bin 0 -> 17 bytes .../command/plugin-hostd-remote_pages-extra | Bin 0 -> 23 bytes .../command/plugin-hostd-remote_pages-refused | Bin 0 -> 17 bytes .../command/plugin-hostd-remote_pages-tabs | Bin 0 -> 19 bytes .../command/plugin-hostd-supervisor_reset | Bin 0 -> 19 bytes .../plugin-hostd-supervisor_reset-extra | Bin 0 -> 28 bytes .../plugin-hostd-supervisor_reset-full | Bin 0 -> 22 bytes .../plugin-hostd-supervisor_reset-refused | Bin 0 -> 19 bytes .../plugin-hostd-supervisor_reset-tabs | Bin 0 -> 21 bytes fuzz/corpus/command/plugin-hostd-track_info | Bin 0 -> 25 bytes .../command/plugin-hostd-track_info-extra | Bin 0 -> 35 bytes .../command/plugin-hostd-track_info-full | Bin 0 -> 29 bytes .../command/plugin-hostd-track_info-refused | Bin 0 -> 25 bytes .../command/plugin-hostd-track_info-tabs | Bin 0 -> 27 bytes fuzz/corpus/command/plugin-hostd-worker_env | Bin 0 -> 24 bytes .../command/plugin-hostd-worker_env-extra | Bin 0 -> 30 bytes .../command/plugin-hostd-worker_env-refused | Bin 0 -> 24 bytes .../command/plugin-hostd-worker_env-tabs | Bin 0 -> 26 bytes fuzz/corpus/command/plugin-hostd-worker_list | Bin 0 -> 14 bytes .../command/plugin-hostd-worker_list-extra | Bin 0 -> 20 bytes .../command/plugin-hostd-worker_list-refused | Bin 0 -> 14 bytes .../command/plugin-hostd-worker_list-tabs | Bin 0 -> 16 bytes fuzz/corpus/command/quit-refused-then-quit | Bin 0 -> 18 bytes fuzz/corpus/command/readme-1 | Bin 0 -> 45 bytes fuzz/corpus/command/readme-2 | Bin 0 -> 21 bytes fuzz/corpus/command/readme-3 | Bin 0 -> 19 bytes fuzz/corpus/command/readme-4 | Bin 0 -> 14 bytes fuzz/corpus/command/readme-session | Bin 0 -> 99 bytes fuzz/corpus/command/remove-all | Bin 0 -> 12 bytes fuzz/corpus/command/trailing-cr-tab | Bin 0 -> 23 bytes fuzz/corpus/reply/buffer-grows | Bin 0 -> 5573 bytes fuzz/corpus/reply/controller-closes | Bin 0 -> 131 bytes fuzz/corpus/reply/controller-connection-reset | Bin 0 -> 97 bytes .../reply/controller-takes-a-few-bytes-a-send | Bin 0 -> 97 bytes fuzz/corpus/reply/event | Bin 0 -> 33 bytes fuzz/corpus/reply/event-cut-at-nul | Bin 0 -> 30 bytes fuzz/corpus/reply/gesture-begun-twice | Bin 0 -> 62 bytes fuzz/corpus/reply/gesture-ended-by-its-worker | Bin 0 -> 60 bytes fuzz/corpus/reply/gesture-held-at-the-end | Bin 0 -> 33 bytes fuzz/corpus/reply/gesture-held-then-removed | Bin 0 -> 35 bytes fuzz/corpus/reply/gesture-long | Bin 0 -> 2031 bytes fuzz/corpus/reply/gesture-not-a-gesture | Bin 0 -> 74 bytes .../reply/gesture-of-a-removed-instance | Bin 0 -> 98 bytes fuzz/corpus/reply/interrupted | Bin 0 -> 68 bytes fuzz/corpus/reply/line-at-the-limit | Bin 0 -> 6007 bytes fuzz/corpus/reply/line-past-the-limit | Bin 0 -> 6068 bytes .../corpus/reply/line-past-the-limit-in-parts | Bin 0 -> 7087 bytes fuzz/corpus/reply/lines | Bin 0 -> 66 bytes fuzz/corpus/reply/split-at-every-byte | Bin 0 -> 302 bytes fuzz/corpus/reply/split-before-every-nul | Bin 0 -> 74 bytes fuzz/corpus/reply/truncated-tail | Bin 0 -> 86 bytes fuzz/corpus/reply/two-workers-interleaved | Bin 0 -> 141 bytes fuzz/corpus/reply/worker-closed | Bin 0 -> 70 bytes fuzz/corpus/reply/workers-past-the-table | Bin 0 -> 72 bytes fuzz/gen_corpus.py | 235 ++++++++++++++++++ 370 files changed, 235 insertions(+) create mode 100644 fuzz/corpus/command/add-default create mode 100644 fuzz/corpus/command/add-fuzz_client create mode 100644 fuzz/corpus/command/add-instance-past-the-table create mode 100644 fuzz/corpus/command/add-own create mode 100644 fuzz/corpus/command/add-own-fuzz_client-extra create mode 100644 fuzz/corpus/command/add-pool-p create mode 100644 fuzz/corpus/command/add-pool-p-fuzz_client create mode 100644 fuzz/corpus/command/add-too-few create mode 100644 fuzz/corpus/command/chain-id-not-a-number create mode 100644 fuzz/corpus/command/chain-not-a-number create mode 100644 fuzz/corpus/command/chain-of-many create mode 100644 fuzz/corpus/command/empty create mode 100644 fuzz/corpus/command/leading-space create mode 100644 fuzz/corpus/command/many-words create mode 100644 fuzz/corpus/command/mod-host-add create mode 100644 fuzz/corpus/command/mod-host-add-extra create mode 100644 fuzz/corpus/command/mod-host-add-full create mode 100644 fuzz/corpus/command/mod-host-add-refused create mode 100644 fuzz/corpus/command/mod-host-add-tabs create mode 100644 fuzz/corpus/command/mod-host-bundle_add create mode 100644 fuzz/corpus/command/mod-host-bundle_add-extra create mode 100644 fuzz/corpus/command/mod-host-bundle_add-refused create mode 100644 fuzz/corpus/command/mod-host-bundle_add-tabs create mode 100644 fuzz/corpus/command/mod-host-bundle_remove create mode 100644 fuzz/corpus/command/mod-host-bundle_remove-extra create mode 100644 fuzz/corpus/command/mod-host-bundle_remove-refused create mode 100644 fuzz/corpus/command/mod-host-bundle_remove-tabs create mode 100644 fuzz/corpus/command/mod-host-bypass create mode 100644 fuzz/corpus/command/mod-host-bypass-extra create mode 100644 fuzz/corpus/command/mod-host-bypass-refused create mode 100644 fuzz/corpus/command/mod-host-bypass-tabs create mode 100644 fuzz/corpus/command/mod-host-cc_map create mode 100644 fuzz/corpus/command/mod-host-cc_map-extra create mode 100644 fuzz/corpus/command/mod-host-cc_map-full create mode 100644 fuzz/corpus/command/mod-host-cc_map-refused create mode 100644 fuzz/corpus/command/mod-host-cc_map-tabs create mode 100644 fuzz/corpus/command/mod-host-cc_unmap create mode 100644 fuzz/corpus/command/mod-host-cc_unmap-extra create mode 100644 fuzz/corpus/command/mod-host-cc_unmap-refused create mode 100644 fuzz/corpus/command/mod-host-cc_unmap-tabs create mode 100644 fuzz/corpus/command/mod-host-cc_value_set create mode 100644 fuzz/corpus/command/mod-host-cc_value_set-extra create mode 100644 fuzz/corpus/command/mod-host-cc_value_set-refused create mode 100644 fuzz/corpus/command/mod-host-cc_value_set-tabs create mode 100644 fuzz/corpus/command/mod-host-connect create mode 100644 fuzz/corpus/command/mod-host-connect-extra create mode 100644 fuzz/corpus/command/mod-host-connect-refused create mode 100644 fuzz/corpus/command/mod-host-connect-tabs create mode 100644 fuzz/corpus/command/mod-host-cpu_load create mode 100644 fuzz/corpus/command/mod-host-cpu_load-extra create mode 100644 fuzz/corpus/command/mod-host-cpu_load-refused create mode 100644 fuzz/corpus/command/mod-host-cpu_load-tabs create mode 100644 fuzz/corpus/command/mod-host-cv_map create mode 100644 fuzz/corpus/command/mod-host-cv_map-extra create mode 100644 fuzz/corpus/command/mod-host-cv_map-refused create mode 100644 fuzz/corpus/command/mod-host-cv_map-tabs create mode 100644 fuzz/corpus/command/mod-host-cv_unmap create mode 100644 fuzz/corpus/command/mod-host-cv_unmap-extra create mode 100644 fuzz/corpus/command/mod-host-cv_unmap-refused create mode 100644 fuzz/corpus/command/mod-host-cv_unmap-tabs create mode 100644 fuzz/corpus/command/mod-host-disconnect create mode 100644 fuzz/corpus/command/mod-host-disconnect-extra create mode 100644 fuzz/corpus/command/mod-host-disconnect-refused create mode 100644 fuzz/corpus/command/mod-host-disconnect-tabs create mode 100644 fuzz/corpus/command/mod-host-feature_enable create mode 100644 fuzz/corpus/command/mod-host-feature_enable-extra create mode 100644 fuzz/corpus/command/mod-host-feature_enable-refused create mode 100644 fuzz/corpus/command/mod-host-feature_enable-tabs create mode 100644 fuzz/corpus/command/mod-host-help create mode 100644 fuzz/corpus/command/mod-host-help-extra create mode 100644 fuzz/corpus/command/mod-host-help-refused create mode 100644 fuzz/corpus/command/mod-host-help-tabs create mode 100644 fuzz/corpus/command/mod-host-hmi_map create mode 100644 fuzz/corpus/command/mod-host-hmi_map-extra create mode 100644 fuzz/corpus/command/mod-host-hmi_map-refused create mode 100644 fuzz/corpus/command/mod-host-hmi_map-tabs create mode 100644 fuzz/corpus/command/mod-host-hmi_unmap create mode 100644 fuzz/corpus/command/mod-host-hmi_unmap-extra create mode 100644 fuzz/corpus/command/mod-host-hmi_unmap-refused create mode 100644 fuzz/corpus/command/mod-host-hmi_unmap-tabs create mode 100644 fuzz/corpus/command/mod-host-licensee create mode 100644 fuzz/corpus/command/mod-host-licensee-extra create mode 100644 fuzz/corpus/command/mod-host-licensee-refused create mode 100644 fuzz/corpus/command/mod-host-licensee-tabs create mode 100644 fuzz/corpus/command/mod-host-load create mode 100644 fuzz/corpus/command/mod-host-load-extra create mode 100644 fuzz/corpus/command/mod-host-load-refused create mode 100644 fuzz/corpus/command/mod-host-load-tabs create mode 100644 fuzz/corpus/command/mod-host-midi_learn create mode 100644 fuzz/corpus/command/mod-host-midi_learn-extra create mode 100644 fuzz/corpus/command/mod-host-midi_learn-refused create mode 100644 fuzz/corpus/command/mod-host-midi_learn-tabs create mode 100644 fuzz/corpus/command/mod-host-midi_map create mode 100644 fuzz/corpus/command/mod-host-midi_map-extra create mode 100644 fuzz/corpus/command/mod-host-midi_map-refused create mode 100644 fuzz/corpus/command/mod-host-midi_map-tabs create mode 100644 fuzz/corpus/command/mod-host-midi_unmap create mode 100644 fuzz/corpus/command/mod-host-midi_unmap-extra create mode 100644 fuzz/corpus/command/mod-host-midi_unmap-refused create mode 100644 fuzz/corpus/command/mod-host-midi_unmap-tabs create mode 100644 fuzz/corpus/command/mod-host-monitor create mode 100644 fuzz/corpus/command/mod-host-monitor-extra create mode 100644 fuzz/corpus/command/mod-host-monitor-refused create mode 100644 fuzz/corpus/command/mod-host-monitor-tabs create mode 100644 fuzz/corpus/command/mod-host-monitor_midi_program create mode 100644 fuzz/corpus/command/mod-host-monitor_midi_program-extra create mode 100644 fuzz/corpus/command/mod-host-monitor_midi_program-refused create mode 100644 fuzz/corpus/command/mod-host-monitor_midi_program-tabs create mode 100644 fuzz/corpus/command/mod-host-monitor_output create mode 100644 fuzz/corpus/command/mod-host-monitor_output-extra create mode 100644 fuzz/corpus/command/mod-host-monitor_output-refused create mode 100644 fuzz/corpus/command/mod-host-monitor_output-tabs create mode 100644 fuzz/corpus/command/mod-host-output_data_ready create mode 100644 fuzz/corpus/command/mod-host-output_data_ready-extra create mode 100644 fuzz/corpus/command/mod-host-output_data_ready-refused create mode 100644 fuzz/corpus/command/mod-host-output_data_ready-tabs create mode 100644 fuzz/corpus/command/mod-host-param_get create mode 100644 fuzz/corpus/command/mod-host-param_get-extra create mode 100644 fuzz/corpus/command/mod-host-param_get-refused create mode 100644 fuzz/corpus/command/mod-host-param_get-tabs create mode 100644 fuzz/corpus/command/mod-host-param_info create mode 100644 fuzz/corpus/command/mod-host-param_info-extra create mode 100644 fuzz/corpus/command/mod-host-param_info-refused create mode 100644 fuzz/corpus/command/mod-host-param_info-tabs create mode 100644 fuzz/corpus/command/mod-host-param_monitor create mode 100644 fuzz/corpus/command/mod-host-param_monitor-extra create mode 100644 fuzz/corpus/command/mod-host-param_monitor-refused create mode 100644 fuzz/corpus/command/mod-host-param_monitor-tabs create mode 100644 fuzz/corpus/command/mod-host-param_set create mode 100644 fuzz/corpus/command/mod-host-param_set-extra create mode 100644 fuzz/corpus/command/mod-host-param_set-refused create mode 100644 fuzz/corpus/command/mod-host-param_set-tabs create mode 100644 fuzz/corpus/command/mod-host-patch_get create mode 100644 fuzz/corpus/command/mod-host-patch_get-extra create mode 100644 fuzz/corpus/command/mod-host-patch_get-refused create mode 100644 fuzz/corpus/command/mod-host-patch_get-tabs create mode 100644 fuzz/corpus/command/mod-host-patch_set create mode 100644 fuzz/corpus/command/mod-host-patch_set-extra create mode 100644 fuzz/corpus/command/mod-host-patch_set-refused create mode 100644 fuzz/corpus/command/mod-host-patch_set-tabs create mode 100644 fuzz/corpus/command/mod-host-preset_load create mode 100644 fuzz/corpus/command/mod-host-preset_load-extra create mode 100644 fuzz/corpus/command/mod-host-preset_load-refused create mode 100644 fuzz/corpus/command/mod-host-preset_load-tabs create mode 100644 fuzz/corpus/command/mod-host-preset_save create mode 100644 fuzz/corpus/command/mod-host-preset_save-extra create mode 100644 fuzz/corpus/command/mod-host-preset_save-refused create mode 100644 fuzz/corpus/command/mod-host-preset_save-tabs create mode 100644 fuzz/corpus/command/mod-host-preset_show create mode 100644 fuzz/corpus/command/mod-host-preset_show-extra create mode 100644 fuzz/corpus/command/mod-host-preset_show-refused create mode 100644 fuzz/corpus/command/mod-host-preset_show-tabs create mode 100644 fuzz/corpus/command/mod-host-quit create mode 100644 fuzz/corpus/command/mod-host-quit-extra create mode 100644 fuzz/corpus/command/mod-host-quit-refused create mode 100644 fuzz/corpus/command/mod-host-quit-tabs create mode 100644 fuzz/corpus/command/mod-host-remote_page_get create mode 100644 fuzz/corpus/command/mod-host-remote_page_get-extra create mode 100644 fuzz/corpus/command/mod-host-remote_page_get-refused create mode 100644 fuzz/corpus/command/mod-host-remote_page_get-tabs create mode 100644 fuzz/corpus/command/mod-host-remote_pages create mode 100644 fuzz/corpus/command/mod-host-remote_pages-extra create mode 100644 fuzz/corpus/command/mod-host-remote_pages-refused create mode 100644 fuzz/corpus/command/mod-host-remote_pages-tabs create mode 100644 fuzz/corpus/command/mod-host-remove create mode 100644 fuzz/corpus/command/mod-host-remove-extra create mode 100644 fuzz/corpus/command/mod-host-remove-refused create mode 100644 fuzz/corpus/command/mod-host-remove-tabs create mode 100644 fuzz/corpus/command/mod-host-save create mode 100644 fuzz/corpus/command/mod-host-save-extra create mode 100644 fuzz/corpus/command/mod-host-save-refused create mode 100644 fuzz/corpus/command/mod-host-save-tabs create mode 100644 fuzz/corpus/command/mod-host-set_bpb create mode 100644 fuzz/corpus/command/mod-host-set_bpb-extra create mode 100644 fuzz/corpus/command/mod-host-set_bpb-refused create mode 100644 fuzz/corpus/command/mod-host-set_bpb-tabs create mode 100644 fuzz/corpus/command/mod-host-set_bpm create mode 100644 fuzz/corpus/command/mod-host-set_bpm-extra create mode 100644 fuzz/corpus/command/mod-host-set_bpm-refused create mode 100644 fuzz/corpus/command/mod-host-set_bpm-tabs create mode 100644 fuzz/corpus/command/mod-host-show_external_ui create mode 100644 fuzz/corpus/command/mod-host-show_external_ui-extra create mode 100644 fuzz/corpus/command/mod-host-show_external_ui-refused create mode 100644 fuzz/corpus/command/mod-host-show_external_ui-tabs create mode 100644 fuzz/corpus/command/mod-host-state_load create mode 100644 fuzz/corpus/command/mod-host-state_load-extra create mode 100644 fuzz/corpus/command/mod-host-state_load-refused create mode 100644 fuzz/corpus/command/mod-host-state_load-tabs create mode 100644 fuzz/corpus/command/mod-host-state_save create mode 100644 fuzz/corpus/command/mod-host-state_save-extra create mode 100644 fuzz/corpus/command/mod-host-state_save-refused create mode 100644 fuzz/corpus/command/mod-host-state_save-tabs create mode 100644 fuzz/corpus/command/mod-host-state_tmpdir create mode 100644 fuzz/corpus/command/mod-host-state_tmpdir-extra create mode 100644 fuzz/corpus/command/mod-host-state_tmpdir-refused create mode 100644 fuzz/corpus/command/mod-host-state_tmpdir-tabs create mode 100644 fuzz/corpus/command/mod-host-track_info create mode 100644 fuzz/corpus/command/mod-host-track_info-extra create mode 100644 fuzz/corpus/command/mod-host-track_info-full create mode 100644 fuzz/corpus/command/mod-host-track_info-refused create mode 100644 fuzz/corpus/command/mod-host-track_info-tabs create mode 100644 fuzz/corpus/command/mod-host-transport create mode 100644 fuzz/corpus/command/mod-host-transport-extra create mode 100644 fuzz/corpus/command/mod-host-transport-refused create mode 100644 fuzz/corpus/command/mod-host-transport-tabs create mode 100644 fuzz/corpus/command/mod-host-transport_sync create mode 100644 fuzz/corpus/command/mod-host-transport_sync-extra create mode 100644 fuzz/corpus/command/mod-host-transport_sync-refused create mode 100644 fuzz/corpus/command/mod-host-transport_sync-tabs create mode 100644 fuzz/corpus/command/nul-inside create mode 100644 fuzz/corpus/command/only-spaces create mode 100644 fuzz/corpus/command/own-host-1 create mode 100644 fuzz/corpus/command/own-host-2 create mode 100644 fuzz/corpus/command/own-host-3 create mode 100644 fuzz/corpus/command/own-host-4 create mode 100644 fuzz/corpus/command/own-host-5 create mode 100644 fuzz/corpus/command/own-host-6 create mode 100644 fuzz/corpus/command/own-host-7 create mode 100644 fuzz/corpus/command/own-host-8 create mode 100644 fuzz/corpus/command/own-host-session create mode 100644 fuzz/corpus/command/pin-clear-refused create mode 100644 fuzz/corpus/command/pin-set-bad-verdict-key create mode 100644 fuzz/corpus/command/pin-set-layout-unknown create mode 100644 fuzz/corpus/command/pin-set-long create mode 100644 fuzz/corpus/command/plugin-hostd-add create mode 100644 fuzz/corpus/command/plugin-hostd-add-extra create mode 100644 fuzz/corpus/command/plugin-hostd-add-full create mode 100644 fuzz/corpus/command/plugin-hostd-add-refused create mode 100644 fuzz/corpus/command/plugin-hostd-add-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-all_notes_off create mode 100644 fuzz/corpus/command/plugin-hostd-all_notes_off-extra create mode 100644 fuzz/corpus/command/plugin-hostd-all_notes_off-refused create mode 100644 fuzz/corpus/command/plugin-hostd-all_notes_off-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-chain create mode 100644 fuzz/corpus/command/plugin-hostd-chain-extra create mode 100644 fuzz/corpus/command/plugin-hostd-chain-full create mode 100644 fuzz/corpus/command/plugin-hostd-chain-refused create mode 100644 fuzz/corpus/command/plugin-hostd-chain-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-chain_clear create mode 100644 fuzz/corpus/command/plugin-hostd-chain_clear-extra create mode 100644 fuzz/corpus/command/plugin-hostd-chain_clear-refused create mode 100644 fuzz/corpus/command/plugin-hostd-chain_clear-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-descriptor_get create mode 100644 fuzz/corpus/command/plugin-hostd-descriptor_get-extra create mode 100644 fuzz/corpus/command/plugin-hostd-descriptor_get-refused create mode 100644 fuzz/corpus/command/plugin-hostd-descriptor_get-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-instance_info create mode 100644 fuzz/corpus/command/plugin-hostd-instance_info-extra create mode 100644 fuzz/corpus/command/plugin-hostd-instance_info-refused create mode 100644 fuzz/corpus/command/plugin-hostd-instance_info-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-lv2_worker_set create mode 100644 fuzz/corpus/command/plugin-hostd-lv2_worker_set-extra create mode 100644 fuzz/corpus/command/plugin-hostd-lv2_worker_set-refused create mode 100644 fuzz/corpus/command/plugin-hostd-lv2_worker_set-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-note_input create mode 100644 fuzz/corpus/command/plugin-hostd-note_input-extra create mode 100644 fuzz/corpus/command/plugin-hostd-note_input-refused create mode 100644 fuzz/corpus/command/plugin-hostd-note_input-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-note_off create mode 100644 fuzz/corpus/command/plugin-hostd-note_off-extra create mode 100644 fuzz/corpus/command/plugin-hostd-note_off-refused create mode 100644 fuzz/corpus/command/plugin-hostd-note_off-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-note_on create mode 100644 fuzz/corpus/command/plugin-hostd-note_on-extra create mode 100644 fuzz/corpus/command/plugin-hostd-note_on-refused create mode 100644 fuzz/corpus/command/plugin-hostd-note_on-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-param_info create mode 100644 fuzz/corpus/command/plugin-hostd-param_info-extra create mode 100644 fuzz/corpus/command/plugin-hostd-param_info-refused create mode 100644 fuzz/corpus/command/plugin-hostd-param_info-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-pin_accept create mode 100644 fuzz/corpus/command/plugin-hostd-pin_accept-extra create mode 100644 fuzz/corpus/command/plugin-hostd-pin_accept-refused create mode 100644 fuzz/corpus/command/plugin-hostd-pin_accept-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-pin_clear create mode 100644 fuzz/corpus/command/plugin-hostd-pin_clear-extra create mode 100644 fuzz/corpus/command/plugin-hostd-pin_clear-refused create mode 100644 fuzz/corpus/command/plugin-hostd-pin_clear-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-pin_expect create mode 100644 fuzz/corpus/command/plugin-hostd-pin_expect-extra create mode 100644 fuzz/corpus/command/plugin-hostd-pin_expect-refused create mode 100644 fuzz/corpus/command/plugin-hostd-pin_expect-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-pin_layout create mode 100644 fuzz/corpus/command/plugin-hostd-pin_layout-extra create mode 100644 fuzz/corpus/command/plugin-hostd-pin_layout-refused create mode 100644 fuzz/corpus/command/plugin-hostd-pin_layout-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-pin_set create mode 100644 fuzz/corpus/command/plugin-hostd-pin_set-extra create mode 100644 fuzz/corpus/command/plugin-hostd-pin_set-full create mode 100644 fuzz/corpus/command/plugin-hostd-pin_set-refused create mode 100644 fuzz/corpus/command/plugin-hostd-pin_set-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-policy_set create mode 100644 fuzz/corpus/command/plugin-hostd-policy_set-extra create mode 100644 fuzz/corpus/command/plugin-hostd-policy_set-refused create mode 100644 fuzz/corpus/command/plugin-hostd-policy_set-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-pool_config create mode 100644 fuzz/corpus/command/plugin-hostd-pool_config-extra create mode 100644 fuzz/corpus/command/plugin-hostd-pool_config-refused create mode 100644 fuzz/corpus/command/plugin-hostd-pool_config-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-preset_get create mode 100644 fuzz/corpus/command/plugin-hostd-preset_get-extra create mode 100644 fuzz/corpus/command/plugin-hostd-preset_get-refused create mode 100644 fuzz/corpus/command/plugin-hostd-preset_get-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-preset_list create mode 100644 fuzz/corpus/command/plugin-hostd-preset_list-extra create mode 100644 fuzz/corpus/command/plugin-hostd-preset_list-refused create mode 100644 fuzz/corpus/command/plugin-hostd-preset_list-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-quarantine_clear create mode 100644 fuzz/corpus/command/plugin-hostd-quarantine_clear-extra create mode 100644 fuzz/corpus/command/plugin-hostd-quarantine_clear-refused create mode 100644 fuzz/corpus/command/plugin-hostd-quarantine_clear-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-remote_page_get create mode 100644 fuzz/corpus/command/plugin-hostd-remote_page_get-extra create mode 100644 fuzz/corpus/command/plugin-hostd-remote_page_get-refused create mode 100644 fuzz/corpus/command/plugin-hostd-remote_page_get-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-remote_pages create mode 100644 fuzz/corpus/command/plugin-hostd-remote_pages-extra create mode 100644 fuzz/corpus/command/plugin-hostd-remote_pages-refused create mode 100644 fuzz/corpus/command/plugin-hostd-remote_pages-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-supervisor_reset create mode 100644 fuzz/corpus/command/plugin-hostd-supervisor_reset-extra create mode 100644 fuzz/corpus/command/plugin-hostd-supervisor_reset-full create mode 100644 fuzz/corpus/command/plugin-hostd-supervisor_reset-refused create mode 100644 fuzz/corpus/command/plugin-hostd-supervisor_reset-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-track_info create mode 100644 fuzz/corpus/command/plugin-hostd-track_info-extra create mode 100644 fuzz/corpus/command/plugin-hostd-track_info-full create mode 100644 fuzz/corpus/command/plugin-hostd-track_info-refused create mode 100644 fuzz/corpus/command/plugin-hostd-track_info-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-worker_env create mode 100644 fuzz/corpus/command/plugin-hostd-worker_env-extra create mode 100644 fuzz/corpus/command/plugin-hostd-worker_env-refused create mode 100644 fuzz/corpus/command/plugin-hostd-worker_env-tabs create mode 100644 fuzz/corpus/command/plugin-hostd-worker_list create mode 100644 fuzz/corpus/command/plugin-hostd-worker_list-extra create mode 100644 fuzz/corpus/command/plugin-hostd-worker_list-refused create mode 100644 fuzz/corpus/command/plugin-hostd-worker_list-tabs create mode 100644 fuzz/corpus/command/quit-refused-then-quit create mode 100644 fuzz/corpus/command/readme-1 create mode 100644 fuzz/corpus/command/readme-2 create mode 100644 fuzz/corpus/command/readme-3 create mode 100644 fuzz/corpus/command/readme-4 create mode 100644 fuzz/corpus/command/readme-session create mode 100644 fuzz/corpus/command/remove-all create mode 100644 fuzz/corpus/command/trailing-cr-tab create mode 100644 fuzz/corpus/reply/buffer-grows create mode 100644 fuzz/corpus/reply/controller-closes create mode 100644 fuzz/corpus/reply/controller-connection-reset create mode 100644 fuzz/corpus/reply/controller-takes-a-few-bytes-a-send create mode 100644 fuzz/corpus/reply/event create mode 100644 fuzz/corpus/reply/event-cut-at-nul create mode 100644 fuzz/corpus/reply/gesture-begun-twice create mode 100644 fuzz/corpus/reply/gesture-ended-by-its-worker create mode 100644 fuzz/corpus/reply/gesture-held-at-the-end create mode 100644 fuzz/corpus/reply/gesture-held-then-removed create mode 100644 fuzz/corpus/reply/gesture-long create mode 100644 fuzz/corpus/reply/gesture-not-a-gesture create mode 100644 fuzz/corpus/reply/gesture-of-a-removed-instance create mode 100644 fuzz/corpus/reply/interrupted create mode 100644 fuzz/corpus/reply/line-at-the-limit create mode 100644 fuzz/corpus/reply/line-past-the-limit create mode 100644 fuzz/corpus/reply/line-past-the-limit-in-parts create mode 100644 fuzz/corpus/reply/lines create mode 100644 fuzz/corpus/reply/split-at-every-byte create mode 100644 fuzz/corpus/reply/split-before-every-nul create mode 100644 fuzz/corpus/reply/truncated-tail create mode 100644 fuzz/corpus/reply/two-workers-interleaved create mode 100644 fuzz/corpus/reply/worker-closed create mode 100644 fuzz/corpus/reply/workers-past-the-table create mode 100644 fuzz/gen_corpus.py diff --git a/fuzz/corpus/command/add-default b/fuzz/corpus/command/add-default new file mode 100644 index 0000000000000000000000000000000000000000..31d107cb9b6c3c30ba823ba0cfaff6ee8baba6d0 GIT binary patch literal 32 ncmZROWk^g(Q7A3Svq~$is literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/chain-of-many b/fuzz/corpus/command/chain-of-many new file mode 100644 index 0000000000000000000000000000000000000000..18e8d043a2546134d1190b08c2e86ab921dcce8b GIT binary patch literal 210 zcmWNLxe)>|5CbV?OYl9?+2t@KkkBeE;XsLIwA#n-@p?Z$UxIa$6pb*$<`B0GB8eiJ z5Mg47O&oHOcavn1O^P%*2vZAhUZ&a(muC$9244-}5J%45Kn8<1!(W zG9}Y851E~`GF<_RP>5m_q$q_c&TzK&;0$Lt!x_$ShBKVw9OvsG&T)=&oZ}qlxWEN2 zaIrqa1uk%b3tZq5m$<|wE^&!VT;dX!xWW~#aD^*e;R;u{!WFJ@jcZ)v8rQhSHLh{J F{{g3zQXc>S literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-add b/fuzz/corpus/command/mod-host-add new file mode 100644 index 0000000000000000000000000000000000000000..2873e123cff7ed074c46fc3549dc6e7fc20b4bf8 GIT binary patch literal 10 RcmZQzXGly*QK(Qb000S;0vP}R literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-add-extra b/fuzz/corpus/command/mod-host-add-extra new file mode 100644 index 0000000000000000000000000000000000000000..19324f3ec59081fb3452db23f6827b90dfbcc6b7 GIT binary patch literal 21 ccmZP&Vn|F$QK(QbP$)^v%uz_KC@D$=060npG5`Po literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-add-full b/fuzz/corpus/command/mod-host-add-full new file mode 100644 index 0000000000000000000000000000000000000000..c8add989dc74986cbf73fe3269f5e763cb8c9222 GIT binary patch literal 15 WcmZSJVMt6#QK(QbP$)^v%mDxyC?OUX%%PfSTsr~m*T+ytip literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-bundle_add-extra b/fuzz/corpus/command/mod-host-bundle_add-extra new file mode 100644 index 0000000000000000000000000000000000000000..bb9db9e7a700cc26df7b7ab73311e5bb37a853df GIT binary patch literal 21 ccmZP&Vn`~@OUX%%PfSTss8C3)C@D$=07G~N@c;k- literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-bundle_add-refused b/fuzz/corpus/command/mod-host-bundle_add-refused new file mode 100644 index 0000000000000000000000000000000000000000..a5ff2fc3adb90a98ec136660e93a85794a0e1797 GIT binary patch literal 15 WcmZSOVMr>?OUX%%PfSTsr~m*UCj_hj literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-bundle_add-tabs b/fuzz/corpus/command/mod-host-bundle_add-tabs new file mode 100644 index 0000000000000000000000000000000000000000..3c4fc211bde5a313b0cec37564a5305055113c4f GIT binary patch literal 17 YcmZSJV@N8^OUX%%PfSVStl;GW04Wj#sQ>@~ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-bundle_remove b/fuzz/corpus/command/mod-host-bundle_remove new file mode 100644 index 0000000000000000000000000000000000000000..ee1aa119d795c61549e6d63bfae8d3532c57a0bf GIT binary patch literal 20 bcmZP&WJoH_OUX%%FG|hLFH2RZP^bU^KQ9KU literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-bundle_remove-extra b/fuzz/corpus/command/mod-host-bundle_remove-extra new file mode 100644 index 0000000000000000000000000000000000000000..c06ac6ebe24a883ae2ccd3a59970129c32ec2300 GIT binary patch literal 26 fcmZP&XGki|OUX%%FG|hLFH2RZ0K(LYlA=TaZuJQB literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-bundle_remove-refused b/fuzz/corpus/command/mod-host-bundle_remove-refused new file mode 100644 index 0000000000000000000000000000000000000000..4a906096ca94c8d4b985bc6eec402f446907d34e GIT binary patch literal 20 bcmZP-WJoH_OUX%%FG|hLFH2RZP^bU^Ka&Qo literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-bundle_remove-tabs b/fuzz/corpus/command/mod-host-bundle_remove-tabs new file mode 100644 index 0000000000000000000000000000000000000000..051e2bea1436cb8026b4d95fc0ea5a5b41674bc8 GIT binary patch literal 22 dcmZP&W=JZ{OUX%%FG|hLFH7aD;H==~0su?t29y8* literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-bypass b/fuzz/corpus/command/mod-host-bypass new file mode 100644 index 0000000000000000000000000000000000000000..b90d3c203a04c3177d1d05daa160d4fe96b5fb79 GIT binary patch literal 13 UcmZSJVo0hiNGvW^Fi%S%lzQK$gI)QXa#L;yPr25$#y literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-cv_unmap-extra b/fuzz/corpus/command/mod-host-cv_unmap-extra new file mode 100644 index 0000000000000000000000000000000000000000..a66c864c3fc8049481600dfe4c4c046dad0ad8b3 GIT binary patch literal 21 ccmZP&Vn{BFFU`wMEKo2|s8C3)C@D$=06=dBm;e9( literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-cv_unmap-refused b/fuzz/corpus/command/mod-host-cv_unmap-refused new file mode 100644 index 0000000000000000000000000000000000000000..72bee48b3d1a2030ff25579886c706f53b80da2c GIT binary patch literal 15 WcmZSOVMs2EFU`wMEKo2|r~m*TGz3!s literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-cv_unmap-tabs b/fuzz/corpus/command/mod-host-cv_unmap-tabs new file mode 100644 index 0000000000000000000000000000000000000000..17b24e480bd0e7e58e2143d9ffb63194093b60c0 GIT binary patch literal 17 YcmZSJV@NKGFU`wMEZ{WYtl;GW04C!EIRF3v literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-disconnect b/fuzz/corpus/command/mod-host-disconnect new file mode 100644 index 0000000000000000000000000000000000000000..05455d42b30f984c3e8ce0ed7dc2d1516677dc93 GIT binary patch literal 17 YcmZSJV@Sy?PR`HEOHD3Os8FZ?04o3mX#fBK literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-disconnect-extra b/fuzz/corpus/command/mod-host-disconnect-extra new file mode 100644 index 0000000000000000000000000000000000000000..43784896cbaf81f398b779d485fb20caf7bbf726 GIT binary patch literal 23 ccmZP&VMxg=PR`HEOHD3Or~ty$ijty408=yvvj6}9 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-disconnect-refused b/fuzz/corpus/command/mod-host-disconnect-refused new file mode 100644 index 0000000000000000000000000000000000000000..72d36c594e4ad7ad9a71eb2589fc1c3d5d2c9984 GIT binary patch literal 17 YcmZSOV@Sy?PR`HEOHD3Os8FZ?04r4mZU6uP literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-disconnect-tabs b/fuzz/corpus/command/mod-host-disconnect-tabs new file mode 100644 index 0000000000000000000000000000000000000000..c5176e761078d99eb0b071ff26168811f16eee38 GIT binary patch literal 19 acmZP&U`WXOv*`Bs8BEf07aSxtpET3 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-feature_enable-extra b/fuzz/corpus/command/mod-host-feature_enable-extra new file mode 100644 index 0000000000000000000000000000000000000000..f7ca23b99786b9bf193b2d7ae3e03fa230fd9f37 GIT binary patch literal 27 icmZROU`R_%EGaEYjZe)>Ov*`Bs8BFaNUbO-N(2CPehBvf literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-feature_enable-refused b/fuzz/corpus/command/mod-host-feature_enable-refused new file mode 100644 index 0000000000000000000000000000000000000000..2d0240b56b5896e8d3f2a76da007189967d5c92c GIT binary patch literal 21 ccmZP-Vn|C(EGaEYjZe)>Ov*`Bs8BEf07e4_vH$=8 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-feature_enable-tabs b/fuzz/corpus/command/mod-host-feature_enable-tabs new file mode 100644 index 0000000000000000000000000000000000000000..d145fa9bd907785899b06ad25c62d31e6e96f7f5 GIT binary patch literal 23 ecmZP&VMt3&EGaEYjZe)>Ov*{+tl%`@b literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-hmi_map-tabs b/fuzz/corpus/command/mod-host-hmi_map-tabs new file mode 100644 index 0000000000000000000000000000000000000000..31da1061faa8a1575fcb99e02029a2a50974ad4f GIT binary patch literal 38 fcmZQjX2{6RjL%Ih;56W@0Ad&fvh_?Mn3oFxncD{6 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-hmi_unmap b/fuzz/corpus/command/mod-host-hmi_unmap new file mode 100644 index 0000000000000000000000000000000000000000..aacc7eb28aa087181f0e25c0813577a05b57b12e GIT binary patch literal 16 XcmZSJWyr|Qj4#d0O)O9_P^bU^BsK)J literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-hmi_unmap-extra b/fuzz/corpus/command/mod-host-hmi_unmap-extra new file mode 100644 index 0000000000000000000000000000000000000000..6e2ff6d56751e5c5791b05163bdfaca422576bc7 GIT binary patch literal 22 dcmZP&X2{6Rj4#d0O)O9_P^eHyttcr<1OP}T2L1p5 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-hmi_unmap-refused b/fuzz/corpus/command/mod-host-hmi_unmap-refused new file mode 100644 index 0000000000000000000000000000000000000000..21554970314c0e18f142fde940ac348c6828a86b GIT binary patch literal 16 XcmZSOWyr|Qj4#d0O)O9_P^bU^B!&dJ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-hmi_unmap-tabs b/fuzz/corpus/command/mod-host-hmi_unmap-tabs new file mode 100644 index 0000000000000000000000000000000000000000..9a31c9aefbc309f3da94391c85f9f4957227a1d6 GIT binary patch literal 18 ZcmZSJXUNFSj4#d0O)TIv;H==~0st)j1fKu^ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-licensee b/fuzz/corpus/command/mod-host-licensee new file mode 100644 index 0000000000000000000000000000000000000000..9e8e9a1ef81993385f0186d92c470346038b9950 GIT binary patch literal 13 UcmZSJV#vu%PR%P$O;s=e02WUJqW}N^ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-licensee-extra b/fuzz/corpus/command/mod-host-licensee-extra new file mode 100644 index 0000000000000000000000000000000000000000..f4db1269cf24dbb90088faa22609a35d2a5e9c17 GIT binary patch literal 19 acmZP&V93c#PR%P$O;s>ZNUbO-N(2Bi#|7;G literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-licensee-refused b/fuzz/corpus/command/mod-host-licensee-refused new file mode 100644 index 0000000000000000000000000000000000000000..595b3f5ca7bddffe16f6e6310142ba640fdbf694 GIT binary patch literal 13 UcmZSOV#vu%PR%P$O;s=e02Yt~r~m)} literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-licensee-tabs b/fuzz/corpus/command/mod-host-licensee-tabs new file mode 100644 index 0000000000000000000000000000000000000000..718cb95ffcf7b187f9551edfea3f880df9a8e282 GIT binary patch literal 15 WcmZSJVaUl$PR%P$P31J;|>Y literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-load b/fuzz/corpus/command/mod-host-load new file mode 100644 index 0000000000000000000000000000000000000000..c3118f98128a39d3c8e35a79415e24c998042107 GIT binary patch literal 9 QcmZQzW5~%*Oi`!+00>k9KL7v# literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-load-extra b/fuzz/corpus/command/mod-host-load-extra new file mode 100644 index 0000000000000000000000000000000000000000..fda6da1a8ef9a7c264e4a9626b7c05528a225216 GIT binary patch literal 15 WcmZSJVaUl(Oi`#%NUbO-N(2BMTm*># literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-load-refused b/fuzz/corpus/command/mod-host-load-refused new file mode 100644 index 0000000000000000000000000000000000000000..e4455e475a6283732ed0354642e93ec1d265e25d GIT binary patch literal 9 QcmZQ&W5~%*Oi`!+00@BsL;wH) literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-load-tabs b/fuzz/corpus/command/mod-host-load-tabs new file mode 100644 index 0000000000000000000000000000000000000000..707126e3e90e6156265e6deee3ce7d1ea684b952 GIT binary patch literal 11 ScmZSJV93c&OyR8H- literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-midi_learn b/fuzz/corpus/command/mod-host-midi_learn new file mode 100644 index 0000000000000000000000000000000000000000..e6006452ff51e536fd444d3c11e31be794ce641f GIT binary patch literal 25 dcmZP&W5~@+$&AlQO)Sb&Fi@yaFwipvVE|Zw26+Gg literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-midi_learn-extra b/fuzz/corpus/command/mod-host-midi_learn-extra new file mode 100644 index 0000000000000000000000000000000000000000..48bf2bf52fda4299082a603a20b1206720bf3a0a GIT binary patch literal 31 icmZROVaUx)$&AlQO)Sb&Fi@yaFwipv@ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-midi_map b/fuzz/corpus/command/mod-host-midi_map new file mode 100644 index 0000000000000000000000000000000000000000..826c19f7919eb284e61c3f4a1b3a9c70b29cf8e6 GIT binary patch literal 27 ccmZROV93o($&AlUEKo2|r~qOh)H4NP0A6ASM*si- literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-midi_map-extra b/fuzz/corpus/command/mod-host-midi_map-extra new file mode 100644 index 0000000000000000000000000000000000000000..6b9f4f20344c6537c4aa81fc1b41e4f648e378c0 GIT binary patch literal 33 hcmZROW5~@+$&AlUEKo2|r~qOh)H4O+)QXa#L;#Ei2$28) literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-midi_map-refused b/fuzz/corpus/command/mod-host-midi_map-refused new file mode 100644 index 0000000000000000000000000000000000000000..958f2f8c75a22091423558bb91b824e21cd7dea8 GIT binary patch literal 27 ccmZRTV93o($&AlUEKo2|r~qOh)H4NP0AA?^OaK4? literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-midi_map-tabs b/fuzz/corpus/command/mod-host-midi_map-tabs new file mode 100644 index 0000000000000000000000000000000000000000..841c6831a37529a2b90723b53e7061646ad5701f GIT binary patch literal 29 ecmZROV#v)*$&AlUEZ{WYtN>yl)H4NPUM>J+cm>t~ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-midi_unmap b/fuzz/corpus/command/mod-host-midi_unmap new file mode 100644 index 0000000000000000000000000000000000000000..8fbde036a23be4845fb9692e69714f6f6599ee57 GIT binary patch literal 17 YcmZSJW5~@+$&4?}%S|j$Fi@xf04eJQ8UO$Q literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-midi_unmap-extra b/fuzz/corpus/command/mod-host-midi_unmap-extra new file mode 100644 index 0000000000000000000000000000000000000000..0d02c7e4252245fcddf8522be755da61d4bac505 GIT binary patch literal 23 ecmZP&VaUx)$&4?}%S|j$Fi@yaNUbO-N(2BOV literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-midi_unmap-tabs b/fuzz/corpus/command/mod-host-midi_unmap-tabs new file mode 100644 index 0000000000000000000000000000000000000000..9d5a6f7439ed7700853d44cc2b9cac0f24e26230 GIT binary patch literal 19 acmZP&V93o($&4?}%S|lcG~le@3%13%13%13%1x literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-output_data_ready-extra b/fuzz/corpus/command/mod-host-output_data_ready-extra new file mode 100644 index 0000000000000000000000000000000000000000..4fbcb1360def220ae23609190ee522d3e3728381 GIT binary patch literal 26 hcmZP&XUH!tDJU(8Pf09Ej4w(}OsP~zttcr<1ORnh33>nk literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-output_data_ready-refused b/fuzz/corpus/command/mod-host-output_data_ready-refused new file mode 100644 index 0000000000000000000000000000000000000000..df1fe236c577b087f23a10e44172151d3a40b540 GIT binary patch literal 20 bcmZP-WXLZqDJU(8Pf09Ej4w(}OsNC_LeK{_ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-output_data_ready-tabs b/fuzz/corpus/command/mod-host-output_data_ready-tabs new file mode 100644 index 0000000000000000000000000000000000000000..63ed77e1b2bcc4805ce5dc9c542f54b956b242a3 GIT binary patch literal 22 dcmZP&X2>rsDJU(8Pf09Ej4w(}OsVAM0sv7O2T1?` literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-param_get b/fuzz/corpus/command/mod-host-param_get new file mode 100644 index 0000000000000000000000000000000000000000..2ae49081281d407abe588210dcf97043ee75b3a9 GIT binary patch literal 16 XcmZSJWhh82O3aN0O3aPV%uCBxFi@xf04ci#6951J literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-param_info-extra b/fuzz/corpus/command/mod-host-param_info-extra new file mode 100644 index 0000000000000000000000000000000000000000..d214efedad81fc2a87590675ec96a6726dfff4ac GIT binary patch literal 23 ecmZP&VJJu}O3aPV%uCBxFi@yaNUbO-N(2B;>jzx` literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-param_info-refused b/fuzz/corpus/command/mod-host-param_info-refused new file mode 100644 index 0000000000000000000000000000000000000000..74067d8e3c7eb4d5266fcfd75c10699b8ca64bf0 GIT binary patch literal 17 YcmZSOV<<>0O3aPV%uCBxFi@xf04fj#7ytkO literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-param_info-tabs b/fuzz/corpus/command/mod-host-param_info-tabs new file mode 100644 index 0000000000000000000000000000000000000000..9126d0d12a1d437631a5ab09e26d503dadb003b1 GIT binary patch literal 19 acmZP&U?@l|O3aPV%uCDXG~le@(o literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-param_set b/fuzz/corpus/command/mod-host-param_set new file mode 100644 index 0000000000000000000000000000000000000000..5e4e11e6c108a6317b2884151fc2a5a46b64cc5c GIT binary patch literal 18 ZcmZSJXDCQ4O3aNfPAyR|P^eI-001so1xx?{ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-param_set-extra b/fuzz/corpus/command/mod-host-param_set-extra new file mode 100644 index 0000000000000000000000000000000000000000..6c59676946a567748940b107bba7008916c94612 GIT binary patch literal 24 dcmZP&Whh82O3aNfPAyR|P^bXH)QXa#L;zLg2bKT; literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-param_set-refused b/fuzz/corpus/command/mod-host-param_set-refused new file mode 100644 index 0000000000000000000000000000000000000000..c3287368eb221e8c9a2ac8c415414f99e669ca0e GIT binary patch literal 18 ZcmZSOXDCQ4O3aNfPAyR|P^eI-001tt1yKM1 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-param_set-tabs b/fuzz/corpus/command/mod-host-param_set-tabs new file mode 100644 index 0000000000000000000000000000000000000000..601cbd2173d403374a7652ba5f191aeec0168ac5 GIT binary patch literal 20 bcmZP&WGF~1O3aNfPA%ay;H==R;N=1UH=P9_ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-patch_get b/fuzz/corpus/command/mod-host-patch_get new file mode 100644 index 0000000000000000000000000000000000000000..033afdeb8b12c2cbe55ada7e06a694dfaf42bbbf GIT binary patch literal 16 XcmZSJWhh82NzRB*Pc2a}P^bU^Bg6!y literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-patch_get-extra b/fuzz/corpus/command/mod-host-patch_get-extra new file mode 100644 index 0000000000000000000000000000000000000000..0dca8d5e7e4d927fe7d5aa87da26e1a9224cc1dd GIT binary patch literal 22 dcmZP&W++H3NzRB*Pc2a}P^eHyttcr<1OP``2JZj> literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-patch_get-refused b/fuzz/corpus/command/mod-host-patch_get-refused new file mode 100644 index 0000000000000000000000000000000000000000..d5fddff006c5272056cd86b65b5d62622ebb0c99 GIT binary patch literal 16 XcmZSOWhh82NzRB*Pc2a}P^bU^BoqXy literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-patch_get-tabs b/fuzz/corpus/command/mod-host-patch_get-tabs new file mode 100644 index 0000000000000000000000000000000000000000..3931cdbcb42788805c95cf359bd7ea37ccc141cf GIT binary patch literal 18 ZcmZSJXDCQ4NzRB*Pc7jz;H==~0st&-1dsp# literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-patch_set b/fuzz/corpus/command/mod-host-patch_set new file mode 100644 index 0000000000000000000000000000000000000000..f9d67a63a5cb9fb978697588ceff626ad3bcc41a GIT binary patch literal 18 ZcmZSJXDCQ4NzRBbPAyR|P^eI-001sj1xo+` literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-patch_set-extra b/fuzz/corpus/command/mod-host-patch_set-extra new file mode 100644 index 0000000000000000000000000000000000000000..bffefd89f5b930369096f94842106e91c965cc3b GIT binary patch literal 24 dcmZP&Whh82NzRBbPAyR|P^bXH)QXa#L;zLV2bBN- literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-patch_set-refused b/fuzz/corpus/command/mod-host-patch_set-refused new file mode 100644 index 0000000000000000000000000000000000000000..fb4c40beb179725cf0b978e91b8207a48692a118 GIT binary patch literal 18 ZcmZSOXDCQ4NzRBbPAyR|P^eI-001to1yBG0 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-patch_set-tabs b/fuzz/corpus/command/mod-host-patch_set-tabs new file mode 100644 index 0000000000000000000000000000000000000000..c9247447102a1209e0de7381e9143b6c7a829189 GIT binary patch literal 20 bcmZP&WGF~1NzRBbPA%ay;H==R;N=1UHSEv90CkX{v literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-preset_show-extra b/fuzz/corpus/command/mod-host-preset_show-extra new file mode 100644 index 0000000000000000000000000000000000000000..748b22d3f28a535573185d9587d52369ea612262 GIT binary patch literal 22 dcmZP&W+*61Elw?oFV4s>SEx`(ttcr<1OQIg2crN0 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-preset_show-refused b/fuzz/corpus/command/mod-host-preset_show-refused new file mode 100644 index 0000000000000000000000000000000000000000..770b4001adc7413efdee1fa7f4bbb11658da2a35 GIT binary patch literal 16 XcmZSOWhf|0Elw?oFV4s>SEv90Cs_qv literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-preset_show-tabs b/fuzz/corpus/command/mod-host-preset_show-tabs new file mode 100644 index 0000000000000000000000000000000000000000..f10dbee813a0e374c45ac458db50946f642e90f8 GIT binary patch literal 18 ZcmZSJXDBF2Elw?oFV4s>=d9r60st|k1zP|B literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-quit b/fuzz/corpus/command/mod-host-quit new file mode 100644 index 0000000000000000000000000000000000000000..debcfb32bb52d5ec2be37d35c97f9270812b82dd GIT binary patch literal 7 OcmZQzVJIxkECB!nhylm| literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-quit-extra b/fuzz/corpus/command/mod-host-quit-extra new file mode 100644 index 0000000000000000000000000000000000000000..ad73a74fe75e98f539ebc5d20371611c0562f7ec GIT binary patch literal 13 UcmZSJVkj)lEKx|UC@D$=02aCg5&!@I literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-quit-refused b/fuzz/corpus/command/mod-host-quit-refused new file mode 100644 index 0000000000000000000000000000000000000000..15f11061d6a0f8eca49233f2dcc57266ecdd69e0 GIT binary patch literal 7 OcmZQ&VJIxkECB!ns{zdb literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-quit-tabs b/fuzz/corpus/command/mod-host-quit-tabs new file mode 100644 index 0000000000000000000000000000000000000000..1552c3e47d9c9a54781896bb7ffc03ee407fe640 GIT binary patch literal 9 QcmZQzV<;@mEaBw>00>M0;Q#;t literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-remote_page_get b/fuzz/corpus/command/mod-host-remote_page_get new file mode 100644 index 0000000000000000000000000000000000000000..ee14c38e52cbeda0beaada634bd72417d8a2e094 GIT binary patch literal 22 dcmZP&W++O{%`ZufFGx&JjZaT4Q7}+2002#J2HOAt literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-remote_page_get-extra b/fuzz/corpus/command/mod-host-remote_page_get-extra new file mode 100644 index 0000000000000000000000000000000000000000..6b190eee001260defa148ad71bf5b55afe3c8f07 GIT binary patch literal 28 hcmZROWGG6_%`ZufFGx&JjZaT4Q7{0))QXa#L;!wn2_*mk literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-remote_page_get-refused b/fuzz/corpus/command/mod-host-remote_page_get-refused new file mode 100644 index 0000000000000000000000000000000000000000..3f654549c4fdf304441ef5acf0cb0a25349adb8e GIT binary patch literal 22 dcmZP-W++O{%`ZufFGx&JjZaT4Q7}+2002$i2H*ey literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-remote_page_get-tabs b/fuzz/corpus/command/mod-host-remote_page_get-tabs new file mode 100644 index 0000000000000000000000000000000000000000..a794cd3bd38dc450d2a97d768acd67da3848f22d GIT binary patch literal 24 fcmZP&WhhF`%`ZufFGx&JjZaT4;WXeh;N=1UT9O9F literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-remote_pages b/fuzz/corpus/command/mod-host-remote_pages new file mode 100644 index 0000000000000000000000000000000000000000..c56f3e0bbb2b39adc3470bc5b481d41ff37989a2 GIT binary patch literal 17 YcmZSJV<<|^%`ZufFGx&JEmkl904wqZS^xk5 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-remote_pages-extra b/fuzz/corpus/command/mod-host-remote_pages-extra new file mode 100644 index 0000000000000000000000000000000000000000..8c42455db3561eb5a42b44735cc4180102abdfa5 GIT binary patch literal 23 ecmZP&VJJ$?%`ZufFGx&JEmkm4NUbO-N(2B?%m<_Z literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-remote_pages-refused b/fuzz/corpus/command/mod-host-remote_pages-refused new file mode 100644 index 0000000000000000000000000000000000000000..39a8f15ae43a7f041a17e5aca1cf6ee440176533 GIT binary patch literal 17 YcmZSOV<<|^%`ZufFGx&JEmkl904zrZUjP6A literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-remote_pages-tabs b/fuzz/corpus/command/mod-host-remote_pages-tabs new file mode 100644 index 0000000000000000000000000000000000000000..3e394891cc373d061bb40e9233e1adcfb69bdf26 GIT binary patch literal 19 acmZP&U?@t>%`ZufFGx&JE#@@f%`Zz;FaQ7!-U8%`Zz;FaQ7#6$0!4 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-remove-tabs b/fuzz/corpus/command/mod-host-remove-tabs new file mode 100644 index 0000000000000000000000000000000000000000..675337f569afa8fc173ed3366e4c717fca6f8828 GIT binary patch literal 13 UcmZSJVkk<@%`Z#kG~nd|02GV@=>Px# literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-save b/fuzz/corpus/command/mod-host-save new file mode 100644 index 0000000000000000000000000000000000000000..aad45351365813bf9822ddfd25216924126d09ab GIT binary patch literal 9 QcmZQzV<=86OI4@<00@r)P5=M^ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-save-extra b/fuzz/corpus/command/mod-host-save-extra new file mode 100644 index 0000000000000000000000000000000000000000..ca63e26e4111927a094cb04360a2ed68bed9867a GIT binary patch literal 15 WcmZSJVJJ>4OI4^)NUbO-N(2BM@dTIv literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-save-refused b/fuzz/corpus/command/mod-host-save-refused new file mode 100644 index 0000000000000000000000000000000000000000..a1a8d1dd2e1273f46d73a17d7ff195f0ad4f2050 GIT binary patch literal 9 QcmZQ&V<=86OI4@<00_JSQvd(} literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-save-tabs b/fuzz/corpus/command/mod-host-save-tabs new file mode 100644 index 0000000000000000000000000000000000000000..9a39e8fac178f32a72e594c0b73c865a8308b1b1 GIT binary patch literal 11 ScmZSJU?@&3OXaNKQZUdn1ppak1D*f? literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-set_bpb-extra b/fuzz/corpus/command/mod-host-set_bpb-extra new file mode 100644 index 0000000000000000000000000000000000000000..decca358de1cfa631340af3ef636e0234a96512b GIT binary patch literal 20 bcmZP&WGGH8iBBp>QZUdnRYQZUdn1ppbV1ET-{ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-set_bpb-tabs b/fuzz/corpus/command/mod-host-set_bpb-tabs new file mode 100644 index 0000000000000000000000000000000000000000..99b99246ba59cfe9787ad625755f30bb84adc7dd GIT binary patch literal 16 XcmZSJWhhQ9iBBp>;xy1R<>dkZAXx*T literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-set_bpm b/fuzz/corpus/command/mod-host-set_bpm new file mode 100644 index 0000000000000000000000000000000000000000..47e9a99d321f9b1f767afcc4726b62fa341262eb GIT binary patch literal 14 VcmZSJW++ZAiBBrXRWQ&q1ppbG1E~N2 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-set_bpm-extra b/fuzz/corpus/command/mod-host-set_bpm-extra new file mode 100644 index 0000000000000000000000000000000000000000..aabbba34f32d9bb964c3c4e98ee7dfeb90756dea GIT binary patch literal 20 bcmZP&WGGH8iBBrXRWQ&qRYdkZAf^MW literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-show_external_ui b/fuzz/corpus/command/mod-host-show_external_ui new file mode 100644 index 0000000000000000000000000000000000000000..60cba3d0e2060cdc98d947a0d65bf2d1a06819bf GIT binary patch literal 21 ccmZP&VkpkYFON^HC`m2KOU#Kc%~UV|07*axBLDyZ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-show_external_ui-extra b/fuzz/corpus/command/mod-host-show_external_ui-extra new file mode 100644 index 0000000000000000000000000000000000000000..436ef551be925ee3d21e1844419e3386101139c4 GIT binary patch literal 27 gcmZROU?|SWFON^HC`m2KOU#Kc%~UW@0P>0w0ek2PZ2$lO literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-show_external_ui-refused b/fuzz/corpus/command/mod-host-show_external_ui-refused new file mode 100644 index 0000000000000000000000000000000000000000..fee24b8da8b312f3ed19cf693d9b9aa142392794 GIT binary patch literal 21 ccmZP-VkpkYFON^HC`m2KOU#Kc%~UV|07hpf~`Fu?flm literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-track_info-full b/fuzz/corpus/command/mod-host-track_info-full new file mode 100644 index 0000000000000000000000000000000000000000..600a611887c9889700d05fef64962723c3f987e0 GIT binary patch literal 24 dcmZP&Whg01OwNwa%uCBxFi@xf!ji(o literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-track_info-refused b/fuzz/corpus/command/mod-host-track_info-refused new file mode 100644 index 0000000000000000000000000000000000000000..783061ff05e66fc9af031d1cb3003ca579072a2d GIT binary patch literal 19 acmZP-U??d{OwNwa%uCBxFi@yar~m*pZw0mh literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-track_info-tabs b/fuzz/corpus/command/mod-host-track_info-tabs new file mode 100644 index 0000000000000000000000000000000000000000..c73546cbd897a0bbc4cce75dc9adcbade516de52 GIT binary patch literal 21 ccmZP&Vkjv}OwNwa%uCDXG~le@tl;GW06!oFg#Z8m literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-transport b/fuzz/corpus/command/mod-host-transport new file mode 100644 index 0000000000000000000000000000000000000000..94a15771c66fc2549a5a6df67bd3127a250d0a54 GIT binary patch literal 22 acmZP&W+*92%quR)FDg+mP%zLl1z`Y1Xa%@8TpeO)ujtBt& literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-transport-refused b/fuzz/corpus/command/mod-host-transport-refused new file mode 100644 index 0000000000000000000000000000000000000000..4b6f3aa7ca3dee4b6e1534741bfc5fc46250a98e GIT binary patch literal 22 acmZP-W+*92%quR)FDg+mP%zLl1z`Y1)&;x( literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-transport-tabs b/fuzz/corpus/command/mod-host-transport-tabs new file mode 100644 index 0000000000000000000000000000000000000000..abca506abc96bb160421ccf210eeb50e4d04ee95 GIT binary patch literal 24 ccmZP&Whg01%quR)FDl_Q;55)P1z}z;08XR@ivR!s literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-transport_sync b/fuzz/corpus/command/mod-host-transport_sync new file mode 100644 index 0000000000000000000000000000000000000000..e023e0d5e1fa1adc46b5112ce41802282a19a248 GIT binary patch literal 19 acmZP&U??d{%quR)FDi*IuFOkTr~m*vat5*h literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-transport_sync-extra b/fuzz/corpus/command/mod-host-transport_sync-extra new file mode 100644 index 0000000000000000000000000000000000000000..5058da401714b028c9427d79587489e122d5d59b GIT binary patch literal 25 fcmZP&V<;&~%quR)FDi*IuFOkTs8C3)015#BYiJ1k literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-transport_sync-refused b/fuzz/corpus/command/mod-host-transport_sync-refused new file mode 100644 index 0000000000000000000000000000000000000000..25b862a8a807ae7d75d930886173a2efb4173b97 GIT binary patch literal 19 acmZP-U??d{%quR)FDi*IuFOkTr~m*v(FV5w literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/mod-host-transport_sync-tabs b/fuzz/corpus/command/mod-host-transport_sync-tabs new file mode 100644 index 0000000000000000000000000000000000000000..99fd0802cc920f89c861aae7d1a01077d52d10ce GIT binary patch literal 21 ccmZP&Vkjv}%quR)FDi*IuFOm3tl;GW07%9Lv;Y7A literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/nul-inside b/fuzz/corpus/command/nul-inside new file mode 100644 index 0000000000000000000000000000000000000000..156a3af354f0753b943f0c0faf3329533a19d50c GIT binary patch literal 20 UcmZP&WGG6_%`Zz;Fn}=(0XQrL&j0`b literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/only-spaces b/fuzz/corpus/command/only-spaces new file mode 100644 index 0000000000000000000000000000000000000000..80ad503c36dd063c78fb42f66c4ffb600bc85a41 GIT binary patch literal 7 OcmZQzVNg)uY0Ae*1&CAWKNG-BT&d)7SFaQ9UUJ6qH literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/own-host-6 b/fuzz/corpus/command/own-host-6 new file mode 100644 index 0000000000000000000000000000000000000000..db58cd5c1269ea33bb02af9dc0c55c02af65e9e1 GIT binary patch literal 22 dcmZP&W++H3O3aNfPAyR|P$)_)$;?-<002mi2DbnJ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/own-host-7 b/fuzz/corpus/command/own-host-7 new file mode 100644 index 0000000000000000000000000000000000000000..3bbba10044a8c076a7a33d776d04f5da1e624d5b GIT binary patch literal 20 bcmZP&WGF~1O3aNIuiJyiWXJh&RBN;p9 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/pin-clear-refused b/fuzz/corpus/command/pin-clear-refused new file mode 100644 index 0000000000000000000000000000000000000000..059c8aa4fc94c20a2c81d607f084b53add11d73d GIT binary patch literal 16 XcmZSNWhltZi%-r;O)OGK%*g=&B=iLp literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/pin-set-bad-verdict-key b/fuzz/corpus/command/pin-set-bad-verdict-key new file mode 100644 index 0000000000000000000000000000000000000000..bb3c83abfb8bc386716e7730d99374760af2fac4 GIT binary patch literal 190 zcmZSB%}|h;7hjxOqEK3tXO&i3Rb^F>Q<|Qcr;wAGqz~fj73bR;PzV(Ab1QUn5-amd VOY{w`C^XDKA+tC?C$S_o1pwghF696K literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/pin-set-layout-unknown b/fuzz/corpus/command/pin-set-layout-unknown new file mode 100644 index 0000000000000000000000000000000000000000..9c9ebb86344efb9b8c64c5a7bbd5bb0e842125f2 GIT binary patch literal 105 wcmZQbV<^bXi!V+sQ7A3Svq~$is>1xf$_ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-chain-full b/fuzz/corpus/command/plugin-hostd-chain-full new file mode 100644 index 0000000000000000000000000000000000000000..0f3db2a3eb2868ebe58a8dce958b1f047c560dcd GIT binary patch literal 14 VcmZSJW=PIR%*<0TR4`C5000%%0{{R3 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-chain-refused b/fuzz/corpus/command/plugin-hostd-chain-refused new file mode 100644 index 0000000000000000000000000000000000000000..7d8e80a0cf50d09cdc20674531f5166630301def GIT binary patch literal 12 TcmZSOWJu0P%*<0TR4@Pl5G(?- literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-chain-tabs b/fuzz/corpus/command/plugin-hostd-chain-tabs new file mode 100644 index 0000000000000000000000000000000000000000..a74ceeeb646282fb1c498e8b9a8742ee2ed46ebc GIT binary patch literal 14 VcmZSJW=PIR%*^97+ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-note_input-extra b/fuzz/corpus/command/plugin-hostd-note_input-extra new file mode 100644 index 0000000000000000000000000000000000000000..daa10d3be9d1ac178e7b52030ee0677663166c8d GIT binary patch literal 21 ccmZP&V#v!cNsZ6UD<~~dFi=RXC@D$=07P;I>;M1& literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-note_input-refused b/fuzz/corpus/command/plugin-hostd-note_input-refused new file mode 100644 index 0000000000000000000000000000000000000000..3ffb5ee0b140fcf3645529c18eda0a774ecf4959 GIT binary patch literal 15 WcmZSOVaUrbNsZ6UD<~~dFaQ7{KLn=$ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-note_input-tabs b/fuzz/corpus/command/plugin-hostd-note_input-tabs new file mode 100644 index 0000000000000000000000000000000000000000..2408722d6a539d6f6d920b01f408ba9fa9d733b9 GIT binary patch literal 17 YcmZSJW5~-dNsZ6UD=01DG~nd|04g8^qyPW_ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-note_off b/fuzz/corpus/command/plugin-hostd-note_off new file mode 100644 index 0000000000000000000000000000000000000000..7e84b1081a77d6ac1fe1809c2621c9483841da88 GIT binary patch literal 22 bcmZP&X2{DgNsZ4>OH(iaLNfyeLjwZOH(iaLNfyeLjwba)QXa#L;zOH(iaLNfyeLjwZOXD;ELNfzSLjwa|E&xXu1o!{| literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-note_on b/fuzz/corpus/command/plugin-hostd-note_on new file mode 100644 index 0000000000000000000000000000000000000000..0915a29538f1676a778ee8a212f811ab30739453 GIT binary patch literal 21 acmZP&V#v!cNsZ6XQ!oHRGXn)f0|Nj!R|MPu literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-note_on-extra b/fuzz/corpus/command/plugin-hostd-note_on-extra new file mode 100644 index 0000000000000000000000000000000000000000..459b84b2b3fdf81ab94cdc8f6cb0c2669ef887fd GIT binary patch literal 27 gcmZROV93iaNsZ6XQ!oHRGXn)f0|SNBijty409{oFCIA2c literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-note_on-refused b/fuzz/corpus/command/plugin-hostd-note_on-refused new file mode 100644 index 0000000000000000000000000000000000000000..3ec0826265b7747b8e8a7c908b0a30bdb07798f1 GIT binary patch literal 21 acmZP-V#v!cNsZ6XQ!oHRGXn)f0|Nj!zy#p{ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-note_on-tabs b/fuzz/corpus/command/plugin-hostd-note_on-tabs new file mode 100644 index 0000000000000000000000000000000000000000..af6609c60ad69dfbbd663fe537ef2df88a8c9847 GIT binary patch literal 23 ccmZP&VaUrbNsZ6X<1_$5GXqXT0|QE3K-sD#$5K&&&e=gA)o4 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-pin_clear-extra b/fuzz/corpus/command/plugin-hostd-pin_clear-extra new file mode 100644 index 0000000000000000000000000000000000000000..2c22daa02d9e643889e352037f207c31aabccc62 GIT binary patch literal 34 pcmZROXDGE3K-sD#$5K&&*Ruttcr<1OUfp3|IgF literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-pin_clear-refused b/fuzz/corpus/command/plugin-hostd-pin_clear-refused new file mode 100644 index 0000000000000000000000000000000000000000..2f483251a4ce5d57750f2f2f8e44708a41d0246c GIT binary patch literal 28 jcmZRTWGKkYi%-r;O)OFE3K-sD#$5K&&&e=gP#f$ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-pin_clear-tabs b/fuzz/corpus/command/plugin-hostd-pin_clear-tabs new file mode 100644 index 0000000000000000000000000000000000000000..96501a0b4ab941eaaafdb1f16701cff63327f96c GIT binary patch literal 30 lcmZROW+=$ai%-r;O)TOpEy}Y>E3K-sD#$5K&&=cH0sxio3J?GQ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-pin_expect b/fuzz/corpus/command/plugin-hostd-pin_expect new file mode 100644 index 0000000000000000000000000000000000000000..c42aaf848995a9504dc81066c83b4d4941837ef4 GIT binary patch literal 93 ncmZRGVkpSWi%+d6NKGzLFi^i_@% literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-pin_layout-refused b/fuzz/corpus/command/plugin-hostd-pin_layout-refused new file mode 100644 index 0000000000000000000000000000000000000000..18855563af90c462c797f1d032933651960bd7b6 GIT binary patch literal 15 WcmZSOVJOJVi_b}{%r7lbFaQ7{5CoQ<|Qcr;wAGqz~fj73bR;PzV(Ab1QUn5-amd MOY{w`C^XCf07FYFX8-^I literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-pin_set-extra b/fuzz/corpus/command/plugin-hostd-pin_set-extra new file mode 100644 index 0000000000000000000000000000000000000000..e5bf9ee10975fcd4ca9fabfd8e5d61af25ba2bd7 GIT binary patch literal 281 zcmZP&V=Tzbi!V+sQ7A3Svq~$isWG0u`W)|n?B$lM6D5O@D6eR)xO#?(> literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-pin_set-full b/fuzz/corpus/command/plugin-hostd-pin_set-full new file mode 100644 index 0000000000000000000000000000000000000000..6f127096e49725d4fbeb21fbdbd61f0517b79e4b GIT binary patch literal 275 zcmZP&U@XYYi!V+sQ7A3Svq~$isWG0u`W)|n?B$lM6006R)LKgr4 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-pin_set-refused b/fuzz/corpus/command/plugin-hostd-pin_set-refused new file mode 100644 index 0000000000000000000000000000000000000000..6370ec71348b7ce0662566bb4141fd647f10f1a5 GIT binary patch literal 181 zcmZSG#88l#7hjxOqEK3tXO&i3Rb^F>Q<|Qcr;wAGqz~fj73bR;PzV(Ab1QUn5-amd MOY{w`C^XCf07ld+YybcN literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-pin_set-tabs b/fuzz/corpus/command/plugin-hostd-pin_set-tabs new file mode 100644 index 0000000000000000000000000000000000000000..591da41fd06a64315aebe0039ab8648e28aedc82 GIT binary patch literal 183 zcmZSB!cdTz7hjxO!dY6BXO&i3Rb^F>Q<|Qc$C;Cvqz~fj73bR;PzX5lb1QUn5-amd OOY{w`C^XD~mkR)P( literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-quarantine_clear-refused b/fuzz/corpus/command/plugin-hostd-quarantine_clear-refused new file mode 100644 index 0000000000000000000000000000000000000000..cbae4f90c1600a60d79e2beac53e4cd3fffc217a GIT binary patch literal 21 ccmZP-Vkj(4EK1BP$;?ZQPtHkAEK)E407!!e1^@s6 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-quarantine_clear-tabs b/fuzz/corpus/command/plugin-hostd-quarantine_clear-tabs new file mode 100644 index 0000000000000000000000000000000000000000..e5e6a5604410f5d5a0024112da672f648625d90a GIT binary patch literal 23 ecmZP&VJIw3EK1BP$;?ZQPtHkAEaEia%`ZufFGx&JE#@@fNUZ=$0sybN3RVCB literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-track_info-full b/fuzz/corpus/command/plugin-hostd-track_info-full new file mode 100644 index 0000000000000000000000000000000000000000..93e8a46fbc0f58d2b7279aae681423b9a90c8ca4 GIT binary patch literal 29 kcmZROVkjv}OwNwa%uCBxFi*#>d| literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-worker_env b/fuzz/corpus/command/plugin-hostd-worker_env new file mode 100644 index 0000000000000000000000000000000000000000..44a04867f72d2ff566ea178f0e3abdca76833858 GIT binary patch literal 24 fcmZP&Whl=t%1$kcPt7Y+NX|(tP%zLnRL}(gTJ{GM literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-worker_env-extra b/fuzz/corpus/command/plugin-hostd-worker_env-extra new file mode 100644 index 0000000000000000000000000000000000000000..240fd6d2aa661a92b69906c41cf12ccb76af3a2b GIT binary patch literal 30 lcmZROW+=}u%1$kcPt7Y+NX|(tP%zLnRM1sOttcr<1OSat30(jH literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-worker_env-refused b/fuzz/corpus/command/plugin-hostd-worker_env-refused new file mode 100644 index 0000000000000000000000000000000000000000..3d9d69bf07063faa909544c48234e0ee7a64a062 GIT binary patch literal 24 fcmZP-Whl=t%1$kcPt7Y+NX|(tP%zLnRL}(gTW$v! literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/plugin-hostd-worker_env-tabs b/fuzz/corpus/command/plugin-hostd-worker_env-tabs new file mode 100644 index 0000000000000000000000000000000000000000..da00526143b3e10270ac5c200c4348cf61fcedc8 GIT binary patch literal 26 hcmZP&XDH7v%1$kcPt7aiOwLIx;55)R)+zjRUMcJuE@j02rB>*^xAPxWk literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/remove-all b/fuzz/corpus/command/remove-all new file mode 100644 index 0000000000000000000000000000000000000000..19071ed565ea818c59792313e95d44050264b08e GIT binary patch literal 12 TcmZSJWGG6_%`Zz;&@}`A5&Huc literal 0 HcmV?d00001 diff --git a/fuzz/corpus/command/trailing-cr-tab b/fuzz/corpus/command/trailing-cr-tab new file mode 100644 index 0000000000000000000000000000000000000000..42c2027fd4c7d8fc1408aa9434b5c24a238d12c0 GIT binary patch literal 23 ecmZP&VJJu}O3aNfPAyR|P)JYA%u_Jq0lZjLt<8 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/controller-connection-reset b/fuzz/corpus/reply/controller-connection-reset new file mode 100644 index 0000000000000000000000000000000000000000..bf8cdf90658241123eaee65d6072c391426c640b GIT binary patch literal 97 zcmd;NU}OkqC`c?y%#ANjEm1H~NKeenQ!vmoH83z>$S*A^C@q0X069Q$BcKFBN@7W3 id|GB+W^o2oZ+dERNoi54f+0kIQfhi;9s?5tD$S*A^C@q0X069Q$BcKFBN@7W3 id|GB+W^o2oZ+dERNoi54f+0kIQfhi;9s?5t3j+XhTO9uY literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/event b/fuzz/corpus/reply/event new file mode 100644 index 0000000000000000000000000000000000000000..cf6b8e1b9b4fde6eec522a413812b7f90b7a566e GIT binary patch literal 33 ocmZRVV<^ur%1$kcPsvP8Q7AW5Ff=kYQ7Fz#&r8g)vQ#ht0H{O>$N&HU literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/event-cut-at-nul b/fuzz/corpus/reply/event-cut-at-nul new file mode 100644 index 0000000000000000000000000000000000000000..8ccdf313e4666be7c4f752b0bb68b7343d0c7547 GIT binary patch literal 30 lcmZRVX2{GdE=kNwPK_^0EiTC~N=;EPP$)NK$STdt1^}1R3J(AP literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/gesture-begun-twice b/fuzz/corpus/reply/gesture-begun-twice new file mode 100644 index 0000000000000000000000000000000000000000..8e8c4cb21608020b9aa46b61b469aee62e210914 GIT binary patch literal 62 rcmZQ%Fk>i4EK1CcPfsl_DJ@D>FjPoS%*<0rN=?tq!zabWz`_6kefkrI literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/gesture-ended-by-its-worker b/fuzz/corpus/reply/gesture-ended-by-its-worker new file mode 100644 index 0000000000000000000000000000000000000000..94bdbf3ff2aa4fdfe88dd23e93d13f69f945ccdd GIT binary patch literal 60 vcmZQ%FkvW2EK1CcPfsl_DJ@D>FjPoS%*<0rN=?tq!!DJYm%_lrz`_6kQw9>Y literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/gesture-held-at-the-end b/fuzz/corpus/reply/gesture-held-at-the-end new file mode 100644 index 0000000000000000000000000000000000000000..ad199936c03a0a8942acab9752e0fc3374efc9dc GIT binary patch literal 33 ocmZQ%kY*@IEK1CcPfsl_DJ@D>FjPoS%*<0rN=?tqV_;$c0H$#X!vFvP literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/gesture-held-then-removed b/fuzz/corpus/reply/gesture-held-then-removed new file mode 100644 index 0000000000000000000000000000000000000000..1903498c845c3d2a4ca672dcc1f2b8208411e1a6 GIT binary patch literal 35 qcmZQ%kY*@IEK1CcPfsl_DJ@D>FjPoS%*<0rN=?tqV_;%nVE_QR{|Uzc literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/gesture-long b/fuzz/corpus/reply/gesture-long new file mode 100644 index 0000000000000000000000000000000000000000..563feeb5d8a18f747a7a740876bc5441316ff651 GIT binary patch literal 2031 zcmZQ%c+OssSd^F>pPpJ=Qd*R%V5m?$3PwXNJ>r5%tIB6uTU^lNW>-s5ocgxU||3Nvw|1i literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/gesture-of-a-removed-instance b/fuzz/corpus/reply/gesture-of-a-removed-instance new file mode 100644 index 0000000000000000000000000000000000000000..6921d641f50fbbbbecf18fd1933cac2afff5afc0 GIT binary patch literal 98 zcmZQzWH4hWNGwXsjZaT4E-5WaRWMXYPt43yNJ>r5%tMnhLY87;U}Tg=Q(&l&R+L%@ M)yu@l%E-b10QaXIU;qFB literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/interrupted b/fuzz/corpus/reply/interrupted new file mode 100644 index 0000000000000000000000000000000000000000..147cb5f4b0175373984a164c92bc6734e5b62204 GIT binary patch literal 68 zcmZQ$U}UgiC`c?y%#ANjEm1H~NKeenQ!vmoH83z>$S*A^C@q0X069Q$BcKFBN@7W3 Od|GB+W^o1s69WK)LK8Ot literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/line-at-the-limit b/fuzz/corpus/reply/line-at-the-limit new file mode 100644 index 0000000000000000000000000000000000000000..b98819318e4bc0ebdd28f16a4c95d1908a14e8ce GIT binary patch literal 6007 zcmeIuF#!ND2m`P(6X@ha{zUL55>lC73>YwAz<>b*1`HT5V8DO@0|pEjFkrxdf%8CK EKBHRwq5uE@ literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/line-past-the-limit b/fuzz/corpus/reply/line-past-the-limit new file mode 100644 index 0000000000000000000000000000000000000000..aa32684ad960f80be12ce12b2c7d61adf885693b GIT binary patch literal 6068 zcmeIuyA6Oa5QE`^C1^7NIw3JnbP}%=gop%*shE)zY(V?Fv)z4-Jl#zYKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5J2F+fFwI@_jxW(-D)8^?MGFW<0|PY-%(55hkD3l#rzUt I$eXnC0!#4`R{#J2 literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/line-past-the-limit-in-parts b/fuzz/corpus/reply/line-past-the-limit-in-parts new file mode 100644 index 0000000000000000000000000000000000000000..8b81d57ee746ba991b2083a95af099a80deeebf0 GIT binary patch literal 7087 zcmeIuF$#k~6olc?)+5;L3FIN9^B{KeK$C0{6CHuTO#i0tg_0 z00IagfB*srAbR|u? literal 0 HcmV?d00001 diff --git a/fuzz/corpus/reply/lines b/fuzz/corpus/reply/lines new file mode 100644 index 0000000000000000000000000000000000000000..f6877c986f91ccb9d802ca272a61cf75af058d9a GIT binary patch literal 66 zcmZQ%uwf`jEK1CcFHS8{Fi=QO%*<0T&@(kKFkr|pEh#81fl2^5Kyf3W1Vc(Edl$L;%87KfbKyf3W f1Vc( +# +# The seed corpus of the two fuzz harnesses (make fuzz-corpus), written from what the repository declares: +# protocol/plugin-hostd.json and protocol/mod-host.json for the command harness, every verb with sample arguments, and +# the send lines of README.md and docs/own-host.md; and, for the reply harness, worker feedback cut every way the +# reader has a branch for. Nothing is fetched and nothing is random: the same tree writes the same files. +# +# Usage: gen_corpus.py (writes /command/ and /reply/) +import json, os, re, shutil, sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.dirname(HERE) + +# ---------------------------------------------------------------- the command harness: [flags] [u16 LE len] [bytes] + +SAMPLE = { + "uri": "urn:fuzz:plugin", "instance": "0", "chain": "1", "name": "p", "max_instances": "4", "format": "clap", + "cpu-list": "0-1", "nice": "-", "path": "lib/fuzz.so", "sha256": "0" * 64, "scheme": "omx-layout/1", + "verdict": "isolated", "worker": "w1", "page": "0", "symbol": "gain", "index": "0", "channel": "0", "key": "60", + "velocity": "100", "#RRGGBB": "#336699", "client_name": "fuzz_client", +} +MOD_HOST_SAMPLE = {"%i": "0", "%s": "x", "%f": "0.5"} + + +def items(syntax): + """the items of a declared syntax: runs with no space outside <...> and [...]""" + out, cur, depth = [], "", 0 + for c in syntax: + if c == " " and depth == 0: + if cur: + out.append(cur) + cur = "" + continue + if c in "<[": + depth += 1 + elif c in ">]" and depth: + depth -= 1 + cur += c + if cur: + out.append(cur) + return out + + +def placeholder(m): + """a sample for ; of the sample for a, or a itself when it is a word of the protocol""" + alternatives = [a.strip() for a in m.group(1).split("|")] + if alternatives[0] in SAMPLE: + return SAMPLE[alternatives[0]] + return alternatives[0] if len(alternatives) > 1 else "x" + + +def word(item, full): + """one sample word for an item of a syntax""" + item = re.sub(r"\[([^\[\]]*)\]", (lambda m: m.group(1).replace("...", "")) if full else "", item) + return re.sub(r"<([^<>]*)>", placeholder, item) + + +def sample(syntax, full): + """sample arguments for a declared syntax: every required word, and with `full` every optional one""" + words, its = [], items(syntax) + i = 0 + while i < len(its): + it = its[i] + i += 1 + if it == "|": + i += 1 + continue + if it.startswith("[") and it.endswith("]"): + if full: + inner = items(it[1:-1]) + words.append(word(inner[0], full)) + continue + words.append(word(it, full)) + return words + + +def message(text, flags=0): + b = text.encode() if isinstance(text, str) else text + return bytes([flags]) + len(b).to_bytes(2, "little") + b + + +def command_corpus(out): + seeds = {} + phd = json.load(open(os.path.join(ROOT, "protocol", "plugin-hostd.json"))) + mod = json.load(open(os.path.join(ROOT, "protocol", "mod-host.json"))) + lines = [] + for v in phd["verbs"] + phd["instance_verbs"]: + lines.append(("plugin-hostd-" + v["name"], [v["name"]] + sample(v["arguments"], False), + [v["name"]] + sample(v["arguments"], True))) + for c in mod["commands"]: + args = [MOD_HOST_SAMPLE.get(a, "x") for a in c["args"]] + lines.append(("mod-host-" + c["verb"], [c["verb"]] + args, [c["verb"]] + args + (["tail"] if c["optional_tail"] else []))) + for name, short, full in lines: + seeds[name] = message(" ".join(short)) + if full != short: + seeds[name + "-full"] = message(" ".join(full)) + # one word too many, the supervisor refusing, and the words split by tabs + seeds[name + "-extra"] = message(" ".join(full + ["extra"])) + seeds[name + "-refused"] = message(" ".join(short), flags=1 | (2 << 1)) + seeds[name + "-tabs"] = message("\t".join(short) + "\r\n") + # the placements of add, one by one + for p in ("own", "default", "pool:p", "pool:p fuzz_client", "fuzz_client", "own fuzz_client extra"): + seeds["add-" + re.sub(r"[^a-z_]", "-", p)] = message("add urn:fuzz:plugin 0 " + p) + seeds["add-instance-past-the-table"] = message("add urn:fuzz:plugin 10000") + seeds["remove-all"] = message("remove -1") + seeds["chain-of-many"] = message("chain 1 " + " ".join(str(i) for i in range(70))) + seeds["chain-not-a-number"] = message("chain 1 0 x") + seeds["chain-id-not-a-number"] = message("chain x 0") + seeds["add-too-few"] = message("add urn:fuzz:plugin") + seeds["pin-set-long"] = message("pin_set urn:fuzz:plugin lib/fuzz.so=%s %s:%s%s" % + ("0" * 64, "omx-layout/1", "0" * 64, " " * 4100)) + seeds["pin-set-layout-unknown"] = message("pin_set urn:fuzz:plugin lib/fuzz.so=%s -" % ("0" * 64)) + seeds["pin-set-bad-verdict-key"] = message("pin_set urn:fuzz:plugin lib/fuzz.so=%s omx-layout/1:%s isolated" % + ("0" * 64, "0" * 64)) + seeds["pin-clear-refused"] = message("pin_clear all", flags=1) + seeds["empty"] = message("") + seeds["only-spaces"] = message(" \r\n") + seeds["nul-inside"] = message(b"remove 0\0remove 1") + seeds["trailing-cr-tab"] = message("param_set 0 gain 6\r\t") + seeds["leading-space"] = message(" param_set 0 gain 6") + seeds["many-words"] = message("param_set 0 " + " ".join("w%d" % i for i in range(80))) + seeds["quit-refused-then-quit"] = message("quit now") + message("quit") + # the send lines of the README and of the own-host page, each alone and as one session + for doc, tag in (("README.md", "readme"), (os.path.join("docs", "own-host.md"), "own-host")): + sends = re.findall(r'^\s*send "([^"]*)"', open(os.path.join(ROOT, doc)).read(), re.M) + for n, line in enumerate(sends): + seeds["%s-%d" % (tag, n + 1)] = message(line) + seeds[tag + "-session"] = b"".join(message(line) for line in sends) + write(out, seeds) + + +# ---------------------------------------------------------------- the reply harness: operations, fuzz/fuzz_reply.c + +def line_limit(): + src = open(os.path.join(HERE, "fuzz_reply.c")).read() + return int(re.search(r"^#define LINE_LIMIT (\d+)$", src, re.M).group(1)) + + +def add(): + return b"\x00" + + +def write_(s, data): + return b"\x01" + bytes([s]) + len(data).to_bytes(2, "little") + data + + +def drain(s): + return b"\x02" + bytes([s]) + + +def closed(s): + return b"\x03" + bytes([s]) + + +def remove(s): + return b"\x04" + bytes([s]) + + +def gone(i): + return b"\x05" + bytes([i]) + + +def eintr(s): + return b"\x06" + bytes([s]) + + +def event(text): + return b"\x07" + len(text).to_bytes(2, "little") + text + + +def short_sends(k): + return b"\x08" + bytes([k]) + + +def controller_gone(closes): + return b"\x09" + bytes([1 if closes else 0]) + + +def reply_corpus(out): + limit = line_limit() + feedback = b"param_set 0 gain 0.5000\0output_set 0 out 0.2500\0data_finish\0" + begin = b"param_gesture 1 gain begin\0" + seeds = { + "lines": write_(0, feedback) + drain(0), + "split-at-every-byte": b"".join(write_(0, feedback[i:i + 1]) for i in range(len(feedback))) + drain(0), + "split-before-every-nul": b"".join(write_(0, part + b"\0") for part in feedback.split(b"\0")[:-1]) + drain(0), + "truncated-tail": write_(0, feedback + b"param_set 0 ga") + drain(0) + closed(0) + drain(0) + remove(0), + "interrupted": eintr(0) + write_(0, feedback) + drain(0), + "gesture-held-then-removed": write_(0, begin) + drain(0) + remove(0), + "gesture-ended-by-its-worker": write_(0, begin + b"param_gesture 1 gain end\0") + drain(0) + remove(0), + "gesture-begun-twice": write_(0, begin + begin) + drain(0) + remove(0), + "gesture-not-a-gesture": write_(0, b"param_gesture begin\0param_gesture_x 1 a begin\0param_gesture 1 a b\0") + + drain(0) + remove(0), + "gesture-of-a-removed-instance": add() + write_(0, begin + b"param_gesture 2 gain begin\0") + drain(0) + + write_(1, b"param_gesture 1 freq begin\0") + drain(1) + gone(1) + remove(0), + "gesture-held-at-the-end": write_(0, begin) + drain(0), + "gesture-long": write_(0, b"param_gesture 1 " + b"s" * 2000 + b" begin\0") + drain(0) + remove(0), + "line-at-the-limit": write_(0, b"x" * limit + b"\0") + drain(0), + "line-past-the-limit": write_(0, b"x" * (limit + 1) + b"\0" + feedback) + drain(0), + "line-past-the-limit-in-parts": write_(0, b"x" * (limit + 1000)) + drain(0) + write_(0, b"y" * 10 + b"\0") + + write_(0, feedback) + drain(0), + "buffer-grows": write_(0, b"x" * 5000) + drain(0) + write_(0, b"x" * 500 + b"\0" + feedback) + drain(0), + "worker-closed": write_(0, feedback) + closed(0) + drain(0) + remove(0), + "two-workers-interleaved": add() + write_(0, feedback[:10]) + write_(1, feedback) + drain(1) + + write_(0, feedback[10:]) + drain(0) + remove(1) + remove(0), + "workers-past-the-table": add() * 6 + write_(3, feedback) + drain(3), + "event": event(b"worker_died w1 1234 signal:9 0"), + "event-cut-at-nul": event(b"instance_restored 0 w1\0junk"), + "controller-takes-a-few-bytes-a-send": short_sends(3) + write_(0, feedback + begin) + drain(0) + remove(0), + "controller-closes": write_(0, begin) + drain(0) + controller_gone(True) + write_(0, feedback) + drain(0) + + event(b"worker_died w1 1 exit:0 0") + remove(0), + "controller-connection-reset": controller_gone(False) + write_(0, feedback + begin) + drain(0) + gone(1), + } + write(out, seeds) + + +def write(out, seeds): + if os.path.isdir(out): + shutil.rmtree(out) + os.makedirs(out) + for name, data in sorted(seeds.items()): + with open(os.path.join(out, name), "wb") as f: + f.write(data) + + +def main(): + if len(sys.argv) != 2: + sys.exit("usage: gen_corpus.py ") + command_corpus(os.path.join(sys.argv[1], "command")) + reply_corpus(os.path.join(sys.argv[1], "reply")) + + +main() From de566e397e72f7a644a15216f5b980b622e68051 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 21:52:10 +0200 Subject: [PATCH 06/17] Build and run the harnesses: make fuzz and make fuzz-run make fuzz builds both harnesses into build/fuzz/ with clang, -fsanitize=fuzzer,address,undefined and -fno-sanitize-recover=all, nothing of the ordinary build touched. make fuzz-run fuzzes each from its corpus for FUZZ_TIME seconds and leaves a crashing input and its log in fuzz/findings/. make fuzz-corpus writes the corpus again, and fuzz-corpus-check fails when the committed one is not what it writes. --- .gitignore | 2 ++ Makefile | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++ fuzz/run.sh | 49 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100755 fuzz/run.sh diff --git a/.gitignore b/.gitignore index 80d0bab..14fbe1f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ tests/gesture-alloc-test tests/lv2-alloc-test tests/lv2-alloc-lv2.o tests/transforms-test +/build/ +fuzz/findings/ diff --git a/Makefile b/Makefile index b2c888c..b01c66d 100644 --- a/Makefile +++ b/Makefile @@ -158,9 +158,75 @@ private-refs: tools/private-refs-check.sh tools/private-refs-check.sh --self-test +# libFuzzer harnesses (fuzz/README.md): the command parser and dispatcher, src/verbs.c with the mod-host side replaced by +# a recorder, and the reply stream, src/relay.c with its sockets answered by the harness; clang with libFuzzer, +# AddressSanitizer and UndefinedBehaviorSanitizer, into build/fuzz/. Nothing of the ordinary build is touched. +# make fuzz build every harness +# make fuzz-run FUZZ_TIME=60 fuzz each one from its seed corpus; red on any crash +# make fuzz-sabotage each mutant of fuzz/mutants/ must make its harness abort on a seed, in its own property +# make fuzz-coverage the coverage witness: llvm-cov over the corpus, held to fuzz/coverage-floor.txt +# make fuzz-corpus write fuzz/corpus/ again from the declared protocol and the README examples +FUZZ_CC ?= clang +FUZZ_TIME ?= 60 +FUZZ_BUILD := build/fuzz +FUZZ_HARNESSES := command reply +FUZZ_SAN := -fsanitize=address,undefined -fno-sanitize-recover=all +FUZZ_CFLAGS := -O1 -g -std=c11 -D_GNU_SOURCE -Wall -Wextra -Werror -fno-omit-frame-pointer +FUZZ_INCS := -Iinclude -Isrc -Ifuzz $(PROTOCOL_CFLAGS) +# what each harness is built from beside its own file: the command harness links the parser and the recorder, the +# reply harness includes src/relay.c itself +FUZZ_SRC_command := src/verbs.c fuzz/recorder.c +FUZZ_SRC_reply := +FUZZ_DEPS := fuzz/fuzz.h fuzz/syntax.h fuzz/recorder.h src/relay.c src/verbs.h include/plugin-hostd/protocol.h + +$(FUZZ_BUILD)/fuzz_%: fuzz/fuzz_%.c $(FUZZ_DEPS) $(FUZZ_SRC_command) + @mkdir -p $(dir $@) + $(FUZZ_CC) $(FUZZ_CFLAGS) $(FUZZ_SAN) -fsanitize=fuzzer $(FUZZ_INCS) $< $(FUZZ_SRC_$*) -pthread -o $@ + +fuzz: $(FUZZ_HARNESSES:%=$(FUZZ_BUILD)/fuzz_%) + +fuzz-run: fuzz + FUZZ_BUILD=$(FUZZ_BUILD) FUZZ_TIME=$(FUZZ_TIME) fuzz/run.sh $(FUZZ_HARNESSES) + +fuzz-sabotage: + FUZZ_BUILD=$(FUZZ_BUILD) MAKE="$(MAKE)" fuzz/sabotage.sh + +# the coverage witness: the same harnesses without sanitizers, instrumented for llvm-cov, each run once over its seed +# corpus (-runs=0 executes the corpus and nothing else); the llvm-cov and llvm-profdata of FUZZ_CC's own LLVM +FUZZ_COV_BUILD := $(FUZZ_BUILD)/cov +FUZZ_COV_FLAGS := -O0 -g -std=c11 -D_GNU_SOURCE -fprofile-instr-generate -fcoverage-mapping +LLVM_COV ?= llvm-cov +LLVM_PROFDATA ?= llvm-profdata + +$(FUZZ_COV_BUILD)/fuzz_%: fuzz/fuzz_%.c $(FUZZ_DEPS) $(FUZZ_SRC_command) + @mkdir -p $(dir $@) + $(FUZZ_CC) $(FUZZ_COV_FLAGS) -fsanitize=fuzzer $(FUZZ_INCS) $< $(FUZZ_SRC_$*) -pthread -o $@ + +fuzz-coverage: $(FUZZ_HARNESSES:%=$(FUZZ_COV_BUILD)/fuzz_%) + @set -e; for h in $(FUZZ_HARNESSES); do \ + rm -f $(FUZZ_COV_BUILD)/$$h.profraw; \ + LLVM_PROFILE_FILE=$(FUZZ_COV_BUILD)/$$h.profraw $(FUZZ_COV_BUILD)/fuzz_$$h -runs=0 fuzz/corpus/$$h/ \ + >$(FUZZ_COV_BUILD)/$$h.log 2>&1 || \ + { cat $(FUZZ_COV_BUILD)/$$h.log; echo "fuzz-coverage: fuzz_$$h failed on its own corpus"; exit 1; }; \ + $(LLVM_PROFDATA) merge -sparse $(FUZZ_COV_BUILD)/$$h.profraw -o $(FUZZ_COV_BUILD)/$$h.profdata; \ + done + @LLVM_COV=$(LLVM_COV) fuzz/coverage.sh $(FUZZ_COV_BUILD) $(FUZZ_HARNESSES) + +# the seed corpus is written from protocol/plugin-hostd.json, protocol/mod-host.json and the send lines of README.md and +# docs/own-host.md, never from a network, and committed; fuzz-corpus-check fails when it is not what they give +fuzz-corpus: + python3 fuzz/gen_corpus.py fuzz/corpus + +fuzz-corpus-check: + @set -e; tmp=$$(mktemp -d); trap 'rm -rf $$tmp' EXIT; \ + python3 fuzz/gen_corpus.py $$tmp; diff -r $$tmp fuzz/corpus; echo "the seed corpus is current" + +.PHONY: fuzz fuzz-run fuzz-sabotage fuzz-coverage fuzz-corpus fuzz-corpus-check + # clean rule clean: @rm -f src/*.o $(PROG) $(PROBE) tests/probe-child tests/lv2-probe-child tests/rt_interpose.so $(FAKE) $(PIN_TEST) $(RELAY_TEST) tests/relay-test-relay.o $(FORMAT_TEST) $(GESTURE_ALLOC_TEST) $(LV2_ALLOC_TEST) tests/lv2-alloc-lv2.o $(TRANSFORMS_TEST) $(GEN) tests/stress.clap tests/jack_levels tests/host_scenarios $(LV2_FIXTURE) + @rm -rf build # the daemon against workers that are not plugin hosts at all (tests/fake-host speaks the same protocol and can be # made to die on cue): placement, forwarding, ledger, replay, attribution, the storm bound; no jack, no plugin diff --git a/fuzz/run.sh b/fuzz/run.sh new file mode 100755 index 0000000..bcba965 --- /dev/null +++ b/fuzz/run.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2026 Pau Aliagas +# +# make fuzz-run: every harness fuzzed for FUZZ_TIME seconds from its seed corpus, one after the other. Red when any +# harness crashes, leaks, times out or runs out of memory; the input that did it is left in +# fuzz/findings/--, and the harness's whole log beside it as fuzz/findings/.log. +# +# New inputs the fuzzer finds go to a scratch corpus under the build directory, never into fuzz/corpus/, so a run +# leaves the tree as it found it. +# +# Usage: FUZZ_BUILD=build/fuzz FUZZ_TIME=60 run.sh ... +set -u +ROOT=$(cd "$(dirname "$0")/.." && pwd) +B=$ROOT/${FUZZ_BUILD:-build/fuzz} +T=${FUZZ_TIME:-60} +FINDINGS=$ROOT/fuzz/findings +mkdir -p "$FINDINGS" + +red= +for h in "$@"; do + bin=$B/fuzz_$h + work=$B/work/$h + rm -rf "$work" + mkdir -p "$work/new" + echo "== fuzz_$h: ${T}s from fuzz/corpus/$h ($(ls "$ROOT/fuzz/corpus/$h" | wc -l) seeds)" + # -jobs/-workers run the fuzzer as one child that logs to fuzz-0.log in the working directory; the parent exits + # non-zero when the child found anything + (cd "$work" && "$bin" -max_total_time="$T" -max_len=65536 -rss_limit_mb=2048 -timeout=25 -jobs=1 -workers=1 \ + -print_final_stats=1 -artifact_prefix="$FINDINGS/$h-" new "$ROOT/fuzz/corpus/$h") + rc=$? + log=$work/fuzz-0.log + if [ $rc -ne 0 ] || grep -qE '^SUMMARY: |ERROR: libFuzzer' "$log" 2>/dev/null; then + cp "$log" "$FINDINGS/$h.log" 2>/dev/null + sed -n '/ERROR: /,/^SUMMARY: /p' "$log" + grep -E 'Test unit written to' "$log" + echo "fuzz_$h: RED (exit $rc); input and log in fuzz/findings/" + red="$red $h" + else + grep -E '^#[0-9]+[[:space:]]+DONE|stat::number_of_executed_units' "$log" | tail -n 2 + echo "fuzz_$h: no crash in ${T}s" + fi +done + +if [ -n "$red" ]; then + echo "make fuzz-run: RED --$red" + exit 1 +fi +echo "make fuzz-run: every harness ran ${T}s without a crash" From a7651ef24328c508bf5656ec20c14b751e342bc0 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 21:52:10 +0200 Subject: [PATCH 07/17] One mutant per property, each found by its own check fuzz/mutants/-.patch is a one-line bug for each property a harness checks. make fuzz-sabotage applies each, rebuilds, runs the harness over its seeds one at a time and is red unless a seed makes it abort in check_; a crash in another property does not count. The source is restored with git checkout on every exit path. --- fuzz/mutants/command-arity.patch | 11 ++++ fuzz/mutants/command-refusal.patch | 11 ++++ fuzz/mutants/command-reply.patch | 11 ++++ fuzz/mutants/command-round-trip.patch | 11 ++++ fuzz/mutants/reply-bounded.patch | 11 ++++ fuzz/mutants/reply-no-loss.patch | 11 ++++ fuzz/mutants/reply-shape.patch | 11 ++++ fuzz/sabotage.sh | 88 +++++++++++++++++++++++++++ 8 files changed, 165 insertions(+) create mode 100644 fuzz/mutants/command-arity.patch create mode 100644 fuzz/mutants/command-refusal.patch create mode 100644 fuzz/mutants/command-reply.patch create mode 100644 fuzz/mutants/command-round-trip.patch create mode 100644 fuzz/mutants/reply-bounded.patch create mode 100644 fuzz/mutants/reply-no-loss.patch create mode 100644 fuzz/mutants/reply-shape.patch create mode 100755 fuzz/sabotage.sh diff --git a/fuzz/mutants/command-arity.patch b/fuzz/mutants/command-arity.patch new file mode 100644 index 0000000..fb2c79f --- /dev/null +++ b/fuzz/mutants/command-arity.patch @@ -0,0 +1,11 @@ +diff --git a/src/verbs.c b/src/verbs.c +index 78daab7..e940605 100644 +--- a/src/verbs.c ++++ b/src/verbs.c +@@ -171,5 +171,5 @@ static char *handle(char *line, char **tok, int ntok) + return ntok == 3 ? sup_lv2_worker_set(tok[1], tok[2]) : sup_resp(ERR_INVALID_OPERATION); + if (!strcmp(verb, PHD_VERB_POOL_CONFIG)) +- return ntok == 3 ? sup_pool_config(tok[1], atoi(tok[2])) : sup_resp(ERR_INVALID_OPERATION); ++ return ntok >= 3 ? sup_pool_config(tok[1], atoi(tok[2])) : sup_resp(ERR_INVALID_OPERATION); + if (!strcmp(verb, PHD_VERB_WORKER_ENV)) + return ntok == 4 ? sup_worker_env(tok[1], tok[2], tok[3]) : sup_resp(ERR_INVALID_OPERATION); diff --git a/fuzz/mutants/command-refusal.patch b/fuzz/mutants/command-refusal.patch new file mode 100644 index 0000000..11f7c3c --- /dev/null +++ b/fuzz/mutants/command-refusal.patch @@ -0,0 +1,11 @@ +diff --git a/src/verbs.c b/src/verbs.c +index 78daab7..066bcf8 100644 +--- a/src/verbs.c ++++ b/src/verbs.c +@@ -142,5 +142,5 @@ static char *handle(char *line, char **tok, int ntok) + reply_text = sup_add(tok[1], atoi(tok[2]), placement, client); + /* the plugin is running: its files are scanned after the reply, never before it */ +- if (reply_text && sup_reply_code(reply_text) >= 0) ++ if (reply_text) + descriptors_add(tok[1]); + return reply_text; diff --git a/fuzz/mutants/command-reply.patch b/fuzz/mutants/command-reply.patch new file mode 100644 index 0000000..e8d6825 --- /dev/null +++ b/fuzz/mutants/command-reply.patch @@ -0,0 +1,11 @@ +diff --git a/src/verbs.c b/src/verbs.c +index 78daab7..6b8af56 100644 +--- a/src/verbs.c ++++ b/src/verbs.c +@@ -118,5 +118,5 @@ static char *chain_words(char **tok, int ntok) + static void reply(int fd, char *text) + { +- socket_send(fd, text, strlen(text) + 1); ++ socket_send(fd, text, strlen(text)); + free(text); + } diff --git a/fuzz/mutants/command-round-trip.patch b/fuzz/mutants/command-round-trip.patch new file mode 100644 index 0000000..85ff376 --- /dev/null +++ b/fuzz/mutants/command-round-trip.patch @@ -0,0 +1,11 @@ +diff --git a/src/verbs.c b/src/verbs.c +index 78daab7..bf45b47 100644 +--- a/src/verbs.c ++++ b/src/verbs.c +@@ -222,5 +222,5 @@ void verbs_receive(msg_t *msg) + *--end = '\0'; + copy = strdup(line); +- for (t = strtok_r(copy, " \t", &save); t && ntok < MAX_TOKENS; t = strtok_r(NULL, " \t", &save)) ++ for (t = strtok_r(copy, " ", &save); t && ntok < MAX_TOKENS; t = strtok_r(NULL, " \t", &save)) + tok[ntok++] = t; + if (ntok == 0) diff --git a/fuzz/mutants/reply-bounded.patch b/fuzz/mutants/reply-bounded.patch new file mode 100644 index 0000000..ebf5804 --- /dev/null +++ b/fuzz/mutants/reply-bounded.patch @@ -0,0 +1,11 @@ +diff --git a/src/relay.c b/src/relay.c +index ff852de..a6be5b0 100644 +--- a/src/relay.c ++++ b/src/relay.c +@@ -216,5 +216,5 @@ static void drain(source_t *s) + s->used -= start - s->buf; + memmove(s->buf, start, s->used); +- if (s->used > LINE_LIMIT) ++ if (s->used > s->cap) + { + s->used = 0; diff --git a/fuzz/mutants/reply-no-loss.patch b/fuzz/mutants/reply-no-loss.patch new file mode 100644 index 0000000..95dea0b --- /dev/null +++ b/fuzz/mutants/reply-no-loss.patch @@ -0,0 +1,11 @@ +diff --git a/src/relay.c b/src/relay.c +index ff852de..3bde252 100644 +--- a/src/relay.c ++++ b/src/relay.c +@@ -205,5 +205,5 @@ static void drain(source_t *s) + { + /* a line too long to relay is dropped whole: its end after a dropped start, or all of it at once */ +- if (s->skip || (size_t)(nul - start) > LINE_LIMIT) ++ if (s->skip || (size_t)(nul - start) >= LINE_LIMIT) + s->skip = 0; + else diff --git a/fuzz/mutants/reply-shape.patch b/fuzz/mutants/reply-shape.patch new file mode 100644 index 0000000..c7054b3 --- /dev/null +++ b/fuzz/mutants/reply-shape.patch @@ -0,0 +1,11 @@ +diff --git a/src/relay.c b/src/relay.c +index ff852de..1b4a79e 100644 +--- a/src/relay.c ++++ b/src/relay.c +@@ -126,5 +126,5 @@ static void gesture_end(const char *held) + { + send_line(held, strlen(held)); +- send_line(" " PHD_GESTURE_END, sizeof(" " PHD_GESTURE_END)); ++ send_line(" " PHD_GESTURE_BEGIN, sizeof(" " PHD_GESTURE_BEGIN)); + } + diff --git a/fuzz/sabotage.sh b/fuzz/sabotage.sh new file mode 100755 index 0000000..dfa2e21 --- /dev/null +++ b/fuzz/sabotage.sh @@ -0,0 +1,88 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2026 Pau Aliagas +# +# make fuzz-sabotage: A PROPERTY NO MUTANT CAN BREAK CHECKS NOTHING. fuzz/mutants/-.patch is a +# one-line bug in the code the harness drives, one for each property the harness checks. This applies each, rebuilds +# the harness, and runs it over its seed corpus one seed at a time; the mutant is found when a seed makes the harness +# abort in check_ (or a check__), the function that holds that property's checks. A crash anywhere else does not count. The +# source is restored with `git checkout --` on every exit path, and the sanitizer build is rebuilt as the tree is at +# the end. +# +# Usage: FUZZ_BUILD=build/fuzz MAKE=make sabotage.sh +set -u +ROOT=$(cd "$(dirname "$0")/.." && pwd) +cd "$ROOT" +B=${FUZZ_BUILD:-build/fuzz} +MAKE=${MAKE:-make} +OUT=$B/sabotage +mkdir -p "$OUT" + +patched= +restore() { + [ -n "$patched" ] && git checkout -- $patched + patched= +} +trap 'restore' EXIT +trap 'restore; exit 130' INT TERM + +if ! git diff --quiet -- src; then + echo "fuzz-sabotage: src/ has uncommitted changes; refusing to patch over them" >&2 + exit 2 +fi + +missed= +n=0 +for p in fuzz/mutants/*.patch; do + m=$(basename "$p" .patch) + h=${m%%-*} + prop=${m#*-} + fn=check_$(echo "$prop" | tr - _) + n=$((n + 1)) + files=$(git apply --numstat "$p" | awk '{ print $3 }') + if ! git apply "$p"; then + echo "$m: $p no longer applies" + missed="$missed $m" + continue + fi + patched=$files + echo "== $m: fuzz_$h against $(grep '^+[^+]' "$p" | sed 's/^+[[:space:]]*//')" + if ! $MAKE --no-print-directory "$B/fuzz_$h" >"$OUT/$m.build.log" 2>&1; then + cat "$OUT/$m.build.log" + echo "$m: the mutant does not build" + missed="$missed $m" + restore + continue + fi + found= + other= + for seed in fuzz/corpus/$h/*; do + "$B/fuzz_$h" -runs=1 -artifact_prefix="$OUT/$m-" "$seed" >"$OUT/$m.log" 2>&1 && continue + if grep -qE "in $fn(_[a-z]+)*[ (]" "$OUT/$m.log"; then + found=$seed + break + fi + other="$other $(basename "$seed")" + done + restore + if [ -n "$found" ]; then + echo "$m: found by $found, in $fn: $(grep -m1 -E '^SUMMARY: ' "$OUT/$m.log")" + else + [ -n "$other" ] && echo "$m: seeds that crashed elsewhere:$other" + echo "$m: MUTANT NOT FOUND -- no seed breaks $fn" + missed="$missed $m" + fi +done + +# leave the sanitizer build as the tree is, not as the last mutant was +$MAKE --no-print-directory fuzz >"$OUT/rebuild.log" 2>&1 || { cat "$OUT/rebuild.log"; exit 1; } + +if [ "$n" -eq 0 ]; then + echo "make fuzz-sabotage: RED -- no mutant in fuzz/mutants/" + exit 1 +fi +if [ -n "$missed" ]; then + echo "make fuzz-sabotage: RED --$missed" + exit 1 +fi +echo "make fuzz-sabotage: all $n mutants found, each by its own property" From 7941320b33b911a5b0a849449ed9c26fca66cab3 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 21:52:10 +0200 Subject: [PATCH 08/17] Hold the harnesses to a coverage floor make fuzz-coverage builds each harness for llvm-cov, runs it once over its corpus and prints the line coverage of the functions it drives and every line no seed reaches. fuzz/coverage-floor.txt holds the covered lines of each, 153 of 153 for the dispatcher and 189 of 210 for the relay, and the target is red below them, or when a property check of a harness is reached by no seed (fuzz/checks.awk, as libreac has it). --- fuzz/checks.awk | 60 ++++++++++++++++++++++++++ fuzz/coverage-floor.txt | 4 ++ fuzz/coverage.sh | 96 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 fuzz/checks.awk create mode 100644 fuzz/coverage-floor.txt create mode 100755 fuzz/coverage.sh diff --git a/fuzz/checks.awk b/fuzz/checks.awk new file mode 100644 index 0000000..839dbd6 --- /dev/null +++ b/fuzz/checks.awk @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2026 Pau Aliagas +# +# THE HARNESS FLOOR (fuzz/coverage.sh). Reads `llvm-cov show` of one harness source +# (`LINE|COUNT|source`) and prints, for every property check in it, one line +# +# :::: +# +# A property check is an abort() and the statement that guards it: the `if` whose +# condition ends in the abort, or, for a bare abort() in a block (an `else` arm, a +# switch's `default:`), the statement that opens that block. is how often the +# seed corpus executed that statement's first line; 0 is a property no seed tests. +# Comments are stripped before anything is matched, so an abort() named in prose is +# not a site. +BEGIN { FS = "|" } +{ + ln = $1 + 0 + cnt = $2 + gsub(/[ \t]/, "", cnt) + src = $0 + sub(/^[^|]*\|[^|]*\|/, "", src) + code = src + if (incomment) { + if (code ~ /\*\//) { sub(/^.*\*\//, "", code); incomment = 0 } + else code = "" + } + while (match(code, /\/\*([^*]|\*+[^*\/])*\*+\//)) + code = substr(code, 1, RSTART - 1) substr(code, RSTART + RLENGTH) + if (code ~ /\/\*/) { sub(/\/\*.*$/, "", code); incomment = 1 } + sub(/\/\/.*$/, "", code) + gsub(/^[ \t]+|[ \t]+$/, "", code) + C[ln] = code; N[ln] = cnt; S[ln] = src + if (code ~ /(^|[^A-Za-z0-9_])abort\(\);/) site[++ns] = ln +} + +# The first line of the statement that ends on line l. +function stmt_start(l) { + while (l > 1 && C[l - 1] != "" && C[l - 1] !~ /[;{}:]$/) l-- + return l +} + +END { + for (i = 1; i <= ns; i++) { + l = site[i] + s = stmt_start(l) + if (s == l && C[l] ~ /^abort\(\);/) { + d = 0 + for (s = l - 1; s >= 1 && d >= 0; s--) + for (k = length(C[s]); k >= 1; k--) { + c = substr(C[s], k, 1) + if (c == "}") d++ + else if (c == "{" && --d < 0) break + } + s = stmt_start(s + 1) + } + t = S[s] + gsub(/^[ \t]+/, "", t) + printf "%s:%d:%d:%s:%s\n", file, s, l, N[s], t + } +} diff --git a/fuzz/coverage-floor.txt b/fuzz/coverage-floor.txt new file mode 100644 index 0000000..3447b90 --- /dev/null +++ b/fuzz/coverage-floor.txt @@ -0,0 +1,4 @@ +# Covered lines each harness's seed corpus reaches (make fuzz-coverage, fuzz/coverage.sh). +# A harness below its floor turns fuzz-coverage red. harness covered-lines +command 153 +reply 189 diff --git a/fuzz/coverage.sh b/fuzz/coverage.sh new file mode 100755 index 0000000..ebfaf8b --- /dev/null +++ b/fuzz/coverage.sh @@ -0,0 +1,96 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2026 Pau Aliagas +# +# THE COVERAGE WITNESS (make fuzz-coverage). Each harness, built for llvm-cov without sanitizers, has run once over its +# seed corpus; this prints the line coverage of the functions the harness exists to drive, and every line of them the +# corpus never reached. The relay's thread, its listening socket and its wake-up pipe are not the reader and are not +# counted. +# +# THE FLOOR. fuzz/coverage-floor.txt holds, per harness, the number of covered lines the committed corpus reaches. A +# harness below its floor turns this red: a seed lost, a harness that stopped driving a function, or code that grew +# lines its corpus never reaches. Raise a floor when coverage rises; lower one only with the change that removed the +# lines. +# +# THE HARNESS FLOOR. Every property check of the harness itself, each abort() in fuzz/fuzz_.c and the +# statement that guards it (fuzz/checks.awk), must be executed by at least one seed: a check no seed reaches is a +# property nobody tests. This names the file and line of every such check and turns red. +# +# Usage: coverage.sh ... (LLVM_COV names llvm-cov) +set -eu +B=$1 +shift +LLVM_COV=${LLVM_COV:-llvm-cov} +FLOOR=fuzz/coverage-floor.txt +[ -f "$FLOOR" ] || { echo "coverage.sh: no $FLOOR" >&2; exit 2; } +low= +dead= + +# the functions each harness covers; static functions are matched by their bare name +funcs() { + case $1 in + command) echo src/verbs.c :: is_instance_verb is_placement pin_set_words number chain_words reply handle \ + verbs_receive ;; + reply) echo src/relay.c :: send_line gesture_seen gesture_end gestures_end gestures_end_instance drain \ + relay_finish relay_event relay_add relay_instance_gone relay_remove ;; + *) echo "coverage.sh: no function list for harness '$1'" >&2; exit 2 ;; + esac +} + +for h in "$@"; do + set -- $(funcs "$h") + files= + while [ "$1" != "::" ]; do files="$files $1"; shift; done + shift + bin=$B/fuzz_$h prof=$B/$h.profdata + echo "== fuzz_$h: $(ls fuzz/corpus/$h | wc -l) seed inputs" + # Name Regions Miss Cover Lines Miss Cover ...: keep the listed functions + tot=$B/$h.covered + "$LLVM_COV" report -show-functions "$bin" -instr-profile="$prof" $files | + awk -v want="$*" -v tot="$tot" ' + BEGIN { n = split(want, w, " "); for (i = 1; i <= n; i++) keep[w[i]] = 1 } + { name = $1; sub(/^.*:/, "", name) } + (name in keep) && NF >= 10 { + printf " %-26s lines %4d missed %3d %s\n", name, $5, $6, $7 + lines += $5; missed += $6; seen[name] = 1 + } + END { + for (k in keep) if (!(k in seen)) printf " %-26s NOT FOUND in the report\n", k + printf " %-26s lines %4d missed %3d %.2f%%\n", "TOTAL", lines, missed, + lines ? 100 * (lines - missed) / lines : 0 + print lines - missed > tot + }' + for f in "$@"; do + "$LLVM_COV" show "$bin" -instr-profile="$prof" -name-regex="^(.*:)?$f\$" $files 2>/dev/null | + awk -v f="$f" -F'|' '$2 ~ /^ *0$/ { sub(/^ */, "", $1); printf " unreached %s:%s %s\n", f, $1, $3 }' + done + src=fuzz/fuzz_$h.c + "$LLVM_COV" show "$bin" -instr-profile="$prof" "$src" | awk -v file="$src" -f fuzz/checks.awk >"$B/$h.checks" + nchk=$(wc -l <"$B/$h.checks") + if [ "$nchk" -eq 0 ]; then + echo " CHECKS: no property check found in $src" + dead="$dead $h" + elif awk -F: '$4 == "0" { n++ } END { exit !n }' "$B/$h.checks"; then + awk -F: '$4 == "0" { t = $0; sub(/^([^:]*:){4}/, "", t) + printf " CHECK: %s:%d no seed reaches (abort at line %d): %s\n", $1, $2, $3, t }' "$B/$h.checks" + dead="$dead $h" + else + echo " checks: all $nchk property checks of $src reached" + fi + covered=$(cat "$tot") + floor=$(awk -v h="$h" '$1 == h { print $2 }' "$FLOOR") + if [ -z "$floor" ]; then + echo " FLOOR: fuzz_$h has no line in $FLOOR" + low="$low $h" + elif [ "$covered" -lt "$floor" ]; then + echo " FLOOR: fuzz_$h covers $covered lines, below its floor of $floor" + low="$low $h" + else + echo " floor $floor: held ($covered covered)" + fi +done + +[ -z "$low" ] || echo "make fuzz-coverage: RED -- below the coverage floor:$low" +[ -z "$dead" ] || echo "make fuzz-coverage: RED -- harness checks no seed reaches:$dead" +[ -z "$low$dead" ] || exit 1 +echo "make fuzz-coverage: every harness at or above its coverage floor, every check reached" From 59d90e84a9af839a7b8f3ffb0675b3183b7a3b57 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 21:52:10 +0200 Subject: [PATCH 09/17] Fuzz on every push and pull request The fuzz workflow builds both harnesses in a fedora:44 container with the mod-host protocol headers of the FreeMixer channel, checks the corpus is current, fuzzes each for 60 s, finds every mutant and holds the coverage floor, and uploads a crashing input as fuzz-findings. --- .github/workflows/fuzz.yml | 67 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/fuzz.yml diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml new file mode 100644 index 0000000..fd98422 --- /dev/null +++ b/.github/workflows/fuzz.yml @@ -0,0 +1,67 @@ +name: fuzz + +# The libFuzzer harnesses of fuzz/ (fuzz/README.md), on every push and pull request: each built with clang under +# AddressSanitizer and UndefinedBehaviorSanitizer and fuzzed for 60 s from its seed corpus, every mutant of +# fuzz/mutants/ found by its own property, the coverage witness held to its floor, and the corpus current. An input +# that crashes a harness is uploaded as the fuzz-findings artifact. The command socket's chunking test stays in ci.yml. + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + fuzz: + name: fuzz the command parser and the reply stream + runs-on: ubuntu-latest + # a backstop: fuzz-run spends 60 s on each harness and fuzz-sabotage runs seeds only, so the job ends well inside it + timeout-minutes: 30 + container: + image: fedora:44 + steps: + - name: Install clang, libFuzzer and llvm-cov + run: | + dnf install -y --setopt=install_weak_deps=False git dnf5-plugins clang compiler-rt llvm make gawk diffutils \ + python3 pkgconf-pkg-config + + - uses: actions/checkout@v4 + + # the harnesses read mod-host's protocol headers: the FreeMixer channel's mod-host-protocol-devel + - name: Install the mod-host protocol headers from the FreeMixer channel + run: | + dnf config-manager addrepo --setopt=retries=10 --from-repofile=https://freemixer.github.io/rpm/freemixer.repo + dnf install -y --setopt=retries=10 mod-host-protocol-devel + + # fuzz-sabotage applies each mutant with git and restores it with git checkout + - name: Let git work in the checkout + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Build the harnesses (clang, libFuzzer, ASan, UBSan) + run: make fuzz + + - name: The seed corpus is what the declared protocol gives + run: make fuzz-corpus-check + + - name: Fuzz each harness for 60 s + run: make fuzz-run FUZZ_TIME=60 + + # a property no mutant can break checks nothing: each one-line mutant must make its harness abort on a seed, in + # that property's own check + - name: Find every mutant, each by its own property + run: make fuzz-sabotage + + # the seed corpus must keep reaching the lines it reaches today (fuzz/coverage-floor.txt) and every property + # check of every harness + - name: Hold the coverage floor + run: make fuzz-coverage + + - name: Upload the crashing inputs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: fuzz-findings + path: fuzz/findings/ + if-no-files-found: ignore From c98fb5eeb68888c4b3d5911f065431ff52d727cf Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 21:52:40 +0200 Subject: [PATCH 10/17] Say what the fuzz harnesses prove and how to run them --- fuzz/README.md | 106 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 fuzz/README.md diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 0000000..7981a65 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,106 @@ +Fuzzing plugin-hostd +==================== + +The daemon reads two streams it does not control: the commands of any local client, and what each worker writes on its +feedback socket. Each has a libFuzzer harness here, built with clang under AddressSanitizer and +UndefinedBehaviorSanitizer and run in CI on every push and pull request (`.github/workflows/fuzz.yml`). + +Each harness is one C file defining `LLVMFuzzerTestOneInput`. It reads its input as a small program, a sequence of +messages or operations, until the input ends; every input means something. No harness opens a socket, starts a +process or a thread, reads a clock, the environment or a file, or calls `dlopen`. Properties are checked with +`if (!cond) abort();`, never `assert()`; every outcome an input can choose returns 0, nothing calls `exit()`, and an +input of 64 MiB or more is refused. The header comment of each harness gives its input format and its properties. + +The harnesses +------------- + +| Harness | Code it drives | Input | +| --- | --- | --- | +| `fuzz_command` | `verbs_receive` and the dispatcher of `src/verbs.c`, with everything below it replaced by the recorder of `fuzz/recorder.c` | up to 32 messages, each a flags byte (accept, or refuse with a declared code) and the message's bytes | +| `fuzz_reply` | the feedback reader of `src/relay.c`: `drain`, the gesture table, `relay_remove`, `relay_instance_gone`, `relay_event` | up to 512 operations: a worker writes, is read, closes, is removed; an instance goes; the controller takes short sends or goes away | + +The recorder stands in for the supervisor, the pins, the descriptors and the socket's send. It answers every call with +the reply its verb declares in `include/plugin-hostd/protocol.h`, or with the refusal the input chose, and folds what an +accepted call changes into a digest of the daemon's state. `fuzz_reply` includes `src/relay.c` and answers its `recv` +and `send` itself, and builds it with a line limit of 6000 bytes so that small inputs reach the drop of a long line. + +What each harness proves, beyond no sanitizer report: + +- **command / reply**: every message gets one reply, a NUL-terminated string of a declared shape, the supervisor's + answer unchanged. +- **command / arity**: a verb the daemon parses itself is acted on only with an argument count its declared syntax + allows. +- **command / round-trip**: a command's words joined by one space, as the declaration writes them, make the same calls, + the same reply and the same state. +- **command / refusal**: a refused command leaves the daemon's state as it was. +- **reply / bounded**: an unfinished line held for a worker never passes the line limit, and its buffer stays bounded. +- **reply / no-loss**: the controller reads every complete line a worker wrote once, in order, however the stream was + cut and wherever its NULs fell, as a reference splitter over the same bytes reads them. +- **reply / shape**: every line the relay writes of its own is an event as written or the end of a gesture still held, + once each. + +The harnesses found three things that are fixed with them: `worker_list`, `supervisor_reset` and `quit` took words +their grammar does not, and the end of a held gesture longer than 1024 bytes went out cut short. + +The seed corpus +--------------- + +`fuzz/corpus//` is written by `fuzz/gen_corpus.py` (`make fuzz-corpus`) and committed. The command seeds are +every verb of `protocol/plugin-hostd.json` and `protocol/mod-host.json` with sample arguments, each with its optional +words, with one word too many, refused by the supervisor and split by tabs, the placements of `add`, and the `send` +lines of `README.md` and `docs/own-host.md`. The reply seeds cut worker feedback at every byte and before every NUL, +hold, end and abandon gestures, and put lines at and past the line limit. Nothing is fetched; `make fuzz-corpus-check` +fails when the committed corpus is not what the generator writes. + +The coverage witness +-------------------- + +`make fuzz-coverage` builds each harness for `llvm-cov`, runs it once over its corpus and prints the line coverage of +the functions it drives, then every line no seed reaches. `fuzz/coverage-floor.txt` holds the covered lines of each +harness, and the target is red below it, or when a property check of a harness file is executed by no seed +(`fuzz/checks.awk`). + +| Harness | Lines | Covered | Lines no seed reaches | +| --- | ---: | ---: | --- | +| command | 153 | 100.00% | | +| reply | 210 | 90.00% | the paths taken when `realloc` fails (`tests/relay_test.c` and `tests/gesture_alloc_test.c` drive them), the relay thread's join and the listening socket in `relay_finish`, and the `fd < 0` guards of `relay_add` and `relay_remove` | + +The mutants +----------- + +`fuzz/mutants/-.patch` is a one-line bug for each property. `make fuzz-sabotage` applies each, +rebuilds the harness, runs it over its seeds one at a time, restores the source with `git checkout --`, and is red +unless a seed made the harness abort in `check_`. + +| Mutant | The bug | Found by | +| --- | --- | --- | +| `command-reply` | the reply is sent without its NUL | `add-default` | +| `command-arity` | `pool_config` takes three words or more | `plugin-hostd-pool_config-extra` | +| `command-round-trip` | the first word is split at spaces only | `mod-host-add-tabs` | +| `command-refusal` | a refused `add` still starts a descriptor scan | `add-instance-past-the-table` | +| `reply-bounded` | the overflow check compares with the capacity, not the line limit | `line-past-the-limit-in-parts` | +| `reply-no-loss` | a line of exactly the line limit is dropped | `line-at-the-limit` | +| `reply-shape` | a held gesture is ended with `begin` | `controller-takes-a-few-bytes-a-send` | + +Running +------- + +``` +make fuzz # build both harnesses into build/fuzz/ +make fuzz-run FUZZ_TIME=60 # fuzz each for 60 s; red on any crash +make fuzz-sabotage # every mutant found by its own property +make fuzz-coverage # the coverage witness +make fuzz-corpus # write fuzz/corpus/ again +``` + +They need clang with libFuzzer (`compiler-rt` on Fedora) and, for `fuzz-coverage`, `llvm-cov` and `llvm-profdata`, and +mod-host's protocol headers, found as for the ordinary build. One harness by hand: + +``` +mkdir -p /tmp/new && build/fuzz/fuzz_reply -max_total_time=60 /tmp/new fuzz/corpus/reply +``` + +`make fuzz-run` writes an input that crashed a harness to `fuzz/findings/-crash-`, with the log beside +it; CI uploads the directory as the `fuzz-findings` artifact. `build/fuzz/fuzz_ ` replays it. Once +the bug is fixed, copy the input into `fuzz/corpus//` under a name that says what it is, and add it to +`fuzz/gen_corpus.py`, so every later run replays it first. From deb0c1b994d49f8c9c36a570932be9508795da33 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 22:25:38 +0200 Subject: [PATCH 11/17] Trim a tab at the end of a command as a space is trimmed The tokenizer takes a tab for whitespace, but the end of a command was trimmed of newlines, carriage returns and spaces only. A command ending in a carriage return and a tab reached the worker with both, and its last word kept the carriage return, while the same command without the tab lost it. A trailing tab is now trimmed too. --- src/verbs.c | 2 +- tests/daemon_test.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/verbs.c b/src/verbs.c index 78daab7..2ad1a81 100644 --- a/src/verbs.c +++ b/src/verbs.c @@ -218,7 +218,7 @@ void verbs_receive(msg_t *msg) int ntok = 0; end = line + strlen(line); - while (end > line && (end[-1] == '\n' || end[-1] == '\r' || end[-1] == ' ')) + while (end > line && (end[-1] == '\n' || end[-1] == '\r' || end[-1] == ' ' || end[-1] == '\t')) *--end = '\0'; copy = strdup(line); for (t = strtok_r(copy, " \t", &save); t && ntok < MAX_TOKENS; t = strtok_r(NULL, " \t", &save)) diff --git a/tests/daemon_test.py b/tests/daemon_test.py index 32740d7..923a60e 100644 --- a/tests/daemon_test.py +++ b/tests/daemon_test.py @@ -46,6 +46,22 @@ def a_word_past_the_grammar_of_a_daemon_verb_is_refused(): d.close() +@test +def the_whitespace_that_ends_a_command_is_not_part_of_its_last_word(): + log = tempfile.mktemp(prefix="plugin-hostd-fake-log.") + d = daemon(env={"FAKE_LOG": log}) + try: + d.expect("add fake:a 0", "resp 0") + # a tab separates words as a space does, so a tab at the end, after a carriage return, is trimmed like one + d.expect("param_set 0 gain 0.5000\r\t", "resp 0") + # read as bytes: a carriage return the worker was sent must not be taken for the end of a line of the log + with open(log, "rb") as f: + got = [l.split(b" ", 1)[1] for l in f.read().split(b"\n") if b" param_set " in l] + check(got == [b"param_set 0 gain 0.5000"], "the worker gets the command without its trailing whitespace: %r" % got) + finally: + d.close() + + @test def placement_own_default_and_pool(): d = daemon() From 152273b7973c5688da53ac0b9e77f041e2f99092 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 22:25:38 +0200 Subject: [PATCH 12/17] Check the round trip of every command, a trailing tab included With a tab at the end trimmed like the rest of the whitespace, a command's words joined by one space are read back as the same words whatever its last word ends in, so the round-trip property no longer leaves out a last word ending in a carriage return or a newline. The seed trailing-cr-tab is the command that was left out. --- fuzz/fuzz_command.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/fuzz/fuzz_command.c b/fuzz/fuzz_command.c index cdcc04d..f57a5f2 100644 --- a/fuzz/fuzz_command.c +++ b/fuzz/fuzz_command.c @@ -29,7 +29,7 @@ * copied into an allocation of exactly its length, with no terminator, as the socket hands it over. * * The words of a message, below, are what the daemon reads: its bytes up to the first NUL, the newlines, carriage - * returns and spaces at its end removed, split at spaces and tabs. Its verb is the first word. + * returns, spaces and tabs at its end removed, split at spaces and tabs. Its verb is the first word. * * Properties, each an abort(): * - reply: every message gets exactly one reply, one NUL-terminated string with no NUL before its end, which is @@ -41,9 +41,7 @@ * argument count its declared syntax allows; a word past PHD_LINE_WORDS_MAX is not read. * Mutant: fuzz/mutants/command-arity.patch, pool_config taking three words or more. * - round-trip: the words of a command the daemon acted on, joined by one space as the declaration writes them, - * make the same calls with the same arguments, the same reply and the same state. A command whose last word ends - * in a carriage return or a newline is left out: the daemon keeps that character when a tab follows it and - * removes it when the line ends there, so its joined words are another command. + * make the same calls with the same arguments, the same reply and the same state. * Mutant: fuzz/mutants/command-round-trip.patch, words split at spaces only. * - refusal: a refused command (its reply's code is negative) leaves the daemon's state as it was: no call the * supervisor took and none of the daemon's own follow-ups (a descriptor scan, the LV2 refusals asked again, quit). @@ -107,7 +105,8 @@ static void words_of(const char *data, size_t size, struct words *out) abort(); memcpy(out->text, data, len); out->text[len] = '\0'; - while (len > 0 && (out->text[len - 1] == '\n' || out->text[len - 1] == '\r' || out->text[len - 1] == ' ')) + while (len > 0 && (out->text[len - 1] == '\n' || out->text[len - 1] == '\r' || out->text[len - 1] == ' ' || + out->text[len - 1] == '\t')) out->text[--len] = '\0'; memcpy(out->copy, out->text, len + 1); out->n = 0; @@ -247,14 +246,10 @@ __attribute__((noinline)) static void check_round_trip(const struct words *w, ui int nfirst, quit_first; uint64_t state_first, host_first; char *reply_first, *line; - const char *last; size_t len; if (w->n == 0 || !acted()) return; - last = w->w[w->n - 1]; - if (last[strlen(last) - 1] == '\r' || last[strlen(last) - 1] == '\n') - return; nfirst = rec_calls_copy(first); quit_first = g_quitting; state_first = g_rec.state; From e1f9d99ea8e7bf234419ac108621f5cd2fd4ce89 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 22:26:01 +0200 Subject: [PATCH 13/17] Test that a long held gesture is ended whole tests/gesture_alloc_test.c holds a 2000-byte gesture and ends it over a socket pair standing for the controller: what arrives is the held line, " end" and one NUL. Before the relay wrote the end in two parts this came out cut at 1024 bytes, with no end word, and the case is red. --- tests/gesture_alloc_test.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/gesture_alloc_test.c b/tests/gesture_alloc_test.c index 2ee0cca..1af0581 100644 --- a/tests/gesture_alloc_test.c +++ b/tests/gesture_alloc_test.c @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include "../src/relay.c" @@ -84,6 +86,33 @@ int main(void) check(s.nheld == 0, "the end of a gesture never held is none"); gestures_end(&s); + + /* a held gesture longer than any fixed buffer is ended whole: its line up to the last word, then " end" */ + { + char line[3000], want[3000], got[4096]; + int sv[2]; + ssize_t n; + + memset(&s, 0, sizeof(s)); + snprintf(line, sizeof(line), "param_gesture 1 %02000d begin", 0); + gesture_seen(&s, line); + check(s.nheld == 1, "positive control: a long begin is held"); + snprintf(want, sizeof(want), "param_gesture 1 %02000d end", 0); + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) + { + perror("socketpair"); + return 1; + } + g_client = sv[0]; + g_client_broken = 0; + gestures_end(&s); + g_client = -1; + n = recv(sv[1], got, sizeof(got), MSG_DONTWAIT); + check(n == (ssize_t)strlen(want) + 1 && !memcmp(got, want, strlen(want) + 1), + "the end of a 2000-byte gesture reaches the controller whole, with its end word and one NUL"); + close(sv[0]); + close(sv[1]); + } printf("gesture table: %d checks, %s\n", checks, failures ? "FAILED" : "ok"); return failures ? 1 : 0; } From 870cb432e65749ee945f306bde9b58bcb53d4100 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 22:27:48 +0200 Subject: [PATCH 14/17] Pass a mutant only when every seed it trips aborts in its own property make fuzz-sabotage stopped at the first seed that aborted in the mutant's own check_ function, so a mutant that also broke another property passed. It now runs every seed and is red unless at least one aborts and each that aborts does so in an abort() whose innermost check_ frame is the property the mutant names; a sanitizer report or another property's check makes the mutant red. --- fuzz/README.md | 5 +++-- fuzz/sabotage.sh | 38 ++++++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/fuzz/README.md b/fuzz/README.md index 7981a65..3b9c3cd 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -69,8 +69,9 @@ The mutants ----------- `fuzz/mutants/-.patch` is a one-line bug for each property. `make fuzz-sabotage` applies each, -rebuilds the harness, runs it over its seeds one at a time, restores the source with `git checkout --`, and is red -unless a seed made the harness abort in `check_`. +rebuilds the harness, runs it over every seed one at a time, restores the source with `git checkout --`, and is red +unless at least one seed makes the harness abort and every seed that does aborts in `check_`: a mutant that +also breaks another property does not pass. | Mutant | The bug | Found by | | --- | --- | --- | diff --git a/fuzz/sabotage.sh b/fuzz/sabotage.sh index dfa2e21..f4cd41d 100755 --- a/fuzz/sabotage.sh +++ b/fuzz/sabotage.sh @@ -4,10 +4,12 @@ # # make fuzz-sabotage: A PROPERTY NO MUTANT CAN BREAK CHECKS NOTHING. fuzz/mutants/-.patch is a # one-line bug in the code the harness drives, one for each property the harness checks. This applies each, rebuilds -# the harness, and runs it over its seed corpus one seed at a time; the mutant is found when a seed makes the harness -# abort in check_ (or a check__), the function that holds that property's checks. A crash anywhere else does not count. The -# source is restored with `git checkout --` on every exit path, and the sanitizer build is rebuilt as the tree is at -# the end. +# the harness, and runs it over every seed of its corpus, one at a time. The mutant is found when at least one seed +# makes the harness abort and every seed that does aborts in check_ (or a check__), the +# function that holds that property's checks: the innermost check_ function of the stack is that one, and the crash +# is an abort(), not a sanitizer report. A seed that crashes anywhere else makes the mutant red, since the mutant then +# breaks a property it does not name. The source is restored with `git checkout --` on every exit path, and the +# sanitizer build is rebuilt as the tree is at the end. # # Usage: FUZZ_BUILD=build/fuzz MAKE=make sabotage.sh set -u @@ -54,21 +56,29 @@ for p in fuzz/mutants/*.patch; do restore continue fi - found= + found=0 + first= other= for seed in fuzz/corpus/$h/*; do - "$B/fuzz_$h" -runs=1 -artifact_prefix="$OUT/$m-" "$seed" >"$OUT/$m.log" 2>&1 && continue - if grep -qE "in $fn(_[a-z]+)*[ (]" "$OUT/$m.log"; then - found=$seed - break + log=$OUT/$m.$(basename "$seed").log + "$B/fuzz_$h" -runs=1 -artifact_prefix="$OUT/$m-" "$seed" >"$log" 2>&1 && { rm -f "$log"; continue; } + # the innermost check_ function of the stack, and whether the harness aborted + where=$(grep -m1 -oE ' in check_[a-z_]+' "$log" | sed 's/^ in //') + if grep -q 'ERROR: libFuzzer: deadly signal' "$log" && echo "$where" | grep -qE "^$fn(_[a-z]+)*\$"; then + found=$((found + 1)) + [ -n "$first" ] || first=$(basename "$seed") + else + other="$other $(basename "$seed")(${where:-$(grep -m1 -E '^SUMMARY: ' "$log")})" fi - other="$other $(basename "$seed")" done restore - if [ -n "$found" ]; then - echo "$m: found by $found, in $fn: $(grep -m1 -E '^SUMMARY: ' "$OUT/$m.log")" + if [ "$found" -gt 0 ] && [ -z "$other" ]; then + echo "$m: found by $found seeds, first $first, every one in $fn" + elif [ -n "$other" ]; then + echo "$m: seeds that crashed outside $fn:$other" + echo "$m: RED -- the mutant breaks a property it does not name (logs in $OUT/)" + missed="$missed $m" else - [ -n "$other" ] && echo "$m: seeds that crashed elsewhere:$other" echo "$m: MUTANT NOT FOUND -- no seed breaks $fn" missed="$missed $m" fi @@ -85,4 +95,4 @@ if [ -n "$missed" ]; then echo "make fuzz-sabotage: RED --$missed" exit 1 fi -echo "make fuzz-sabotage: all $n mutants found, each by its own property" +echo "make fuzz-sabotage: all $n mutants found, each only by its own property" From e5d715eb58c44fd80c5848dd9fd2d6468f7e2858 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 22:30:05 +0200 Subject: [PATCH 15/17] Build against mod-host protocol headers of release 11 or newer protocol/mod-host.json is generated from mod-host's headers and the build's check regenerates it; only the headers carrying param_info, remote_pages and track_info (mod-audio/mod-host#109) give it back unchanged. In the FreeMixer channel those are mod-host-protocol-devel 0.10.6.f14a230-11 (audinux's 11 and 12 carry them too) and libmod-host-protocol-dev 0.1.0-5: every earlier release there gives another mod-host.json, and those before 0.10.6.f14a230-8 do not compile tests/fake_host.c. The spec and debian/control declare these floors, and the fuzz workflow installs the headers at the spec's floor. --- .github/workflows/fuzz.yml | 8 ++++++-- debian/control | 2 +- packaging/plugin-hostd.spec | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index fd98422..2e4ea59 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -29,11 +29,15 @@ jobs: - uses: actions/checkout@v4 - # the harnesses read mod-host's protocol headers: the FreeMixer channel's mod-host-protocol-devel + # the harnesses read mod-host's protocol headers: the FreeMixer channel's mod-host-protocol-devel, at or above the + # floor the spec declares for the build - name: Install the mod-host protocol headers from the FreeMixer channel run: | + set -euo pipefail dnf config-manager addrepo --setopt=retries=10 --from-repofile=https://freemixer.github.io/rpm/freemixer.repo - dnf install -y --setopt=retries=10 mod-host-protocol-devel + floor=$(sed -n 's/^BuildRequires:[[:space:]]*mod-host-protocol-devel[[:space:]]*>=[[:space:]]*\([^[:space:]]*\).*/\1/p' packaging/plugin-hostd.spec | head -n 1) + test -n "$floor" + dnf install -y --setopt=retries=10 "mod-host-protocol-devel >= $floor" # fuzz-sabotage applies each mutant with git and restores it with git checkout - name: Let git work in the checkout diff --git a/debian/control b/debian/control index 0f31858..8272a97 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: sound Priority: optional Maintainer: Pau Aliagas Build-Depends: debhelper-compat (= 13), - libmod-host-protocol-dev, + libmod-host-protocol-dev (>= 0.1.0-5), libomx-contract-dev (>= 3.0.0~), omx-clap-host-tests (>= 0.9.0~) , pkgconf, diff --git a/packaging/plugin-hostd.spec b/packaging/plugin-hostd.spec index 6f2a130..8d1176d 100644 --- a/packaging/plugin-hostd.spec +++ b/packaging/plugin-hostd.spec @@ -13,7 +13,9 @@ BuildRequires: gcc BuildRequires: make BuildRequires: diffutils BuildRequires: python3 -BuildRequires: pkgconfig(mod-host-protocol) +# mod-host's protocol headers: param_info, remote_pages and track_info (mod-audio/mod-host#109) are in them from +# release 11 on, and protocol/mod-host.json is generated from them; a floor only +BuildRequires: mod-host-protocol-devel >= 0.10.6.f14a230-11 # the probe runner's default rates, the protocol's parameter scales and the probe output's shared words are read from # omx-contract's C headers, and the format tables are items of its schema 3 (3.0.0 is the first with the formatMap # kind, the extension bag and plugin.doc); a floor only From e5b540a2d1e72f1c17b72c0363a3e2161c11df24 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 22:34:13 +0200 Subject: [PATCH 16/17] Name the trailing tab among what the harnesses found --- fuzz/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fuzz/README.md b/fuzz/README.md index 3b9c3cd..70b94c0 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -39,8 +39,9 @@ What each harness proves, beyond no sanitizer report: - **reply / shape**: every line the relay writes of its own is an event as written or the end of a gesture still held, once each. -The harnesses found three things that are fixed with them: `worker_list`, `supervisor_reset` and `quit` took words -their grammar does not, and the end of a held gesture longer than 1024 bytes went out cut short. +The harnesses found what is fixed with them: `worker_list`, `supervisor_reset` and `quit` took words their grammar +does not, a tab at the end of a command was not trimmed as a space is, and the end of a held gesture longer than 1024 +bytes went out cut short. The seed corpus --------------- From 9d9089bec1a6a7e5c21eda465951572fe9168551 Mon Sep 17 00:00:00 2001 From: Pau Aliagas Date: Sat, 24 Oct 2026 22:40:29 +0200 Subject: [PATCH 17/17] release: plugin-hostd 0.9.2 worker_list, supervisor_reset and quit refuse a word past their grammar with -902, a trailing tab is trimmed like a space, and a held gesture longer than 1024 bytes is ended whole. The build needs the mod-host protocol headers of release 11 (0.1.0-5 for the DEBs) or newer. The command parser and the reply stream have fuzz harnesses (#144). --- debian/changelog | 19 +++++++++++++++++++ doc/plugin-hostd-probe.1 | 2 +- doc/plugin-hostd.1 | 2 +- packaging/plugin-hostd.spec | 18 +++++++++++++++++- src/main.c | 2 +- 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/debian/changelog b/debian/changelog index e3903ae..7582f5a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,22 @@ +plugin-hostd (0.9.2) unstable; urgency=medium + + * worker_list, supervisor_reset and quit answer -902 to a word past + their grammar instead of acting on it: worker_list x no longer lists, + supervisor_reset all x no longer resets, and quit now no longer stops + the daemon. + * A tab at the end of a command is trimmed like a space: a command + ending in a carriage return and a tab no longer reaches the worker with + both. + * The end of a held gesture longer than 1024 bytes reaches the controller + whole, with its end word; before, it was cut short and the control stayed + held. + * The build needs mod-host's protocol headers of mod-host-protocol-devel + 0.10.6.f14a230-11 or newer (libmod-host-protocol-dev 0.1.0-5 or newer), the + first with param_info, remote_pages and track_info. + * Fuzz harnesses for the command parser and the reply stream (#144). + + -- Pau Aliagas Sat, 24 Oct 2026 22:40:03 +0200 + plugin-hostd (0.9.1) unstable; urgency=medium * The preset schema, protocol/preset.schema.json, is installed in diff --git a/doc/plugin-hostd-probe.1 b/doc/plugin-hostd-probe.1 index ac8e62e..2a96b9f 100644 --- a/doc/plugin-hostd-probe.1 +++ b/doc/plugin-hostd-probe.1 @@ -1,4 +1,4 @@ -.TH PLUGIN\-HOSTD\-PROBE 1 "October 2026" "plugin-hostd 0.9.1" "User Commands" +.TH PLUGIN\-HOSTD\-PROBE 1 "October 2026" "plugin-hostd 0.9.2" "User Commands" .SH NAME plugin\-hostd\-probe \- run a plugin prober under limits and write what happened .SH SYNOPSIS diff --git a/doc/plugin-hostd.1 b/doc/plugin-hostd.1 index 934b4da..2a6a959 100644 --- a/doc/plugin-hostd.1 +++ b/doc/plugin-hostd.1 @@ -1,4 +1,4 @@ -.TH PLUGIN\-HOSTD 1 "October 2026" "plugin-hostd 0.9.1" "User Commands" +.TH PLUGIN\-HOSTD 1 "October 2026" "plugin-hostd 0.9.2" "User Commands" .SH NAME plugin\-hostd \- supervisor of plugin host processes behind one mod\-host protocol socket .SH SYNOPSIS diff --git a/packaging/plugin-hostd.spec b/packaging/plugin-hostd.spec index 8d1176d..a190022 100644 --- a/packaging/plugin-hostd.spec +++ b/packaging/plugin-hostd.spec @@ -1,7 +1,7 @@ %bcond_without check Name: plugin-hostd -Version: 0.9.1 +Version: 0.9.2 Release: 1%{?dist} License: GPL-3.0-or-later Summary: Run LV2 and CLAP plugins in a live rig without one crash taking down the show @@ -116,6 +116,22 @@ make test-daemon check-generated %{_datadir}/omx-contract/contract.d/3/plugin-hostd/ %changelog +* Sat Oct 24 2026 Pau Aliagas - 0.9.2-1 +- worker_list, supervisor_reset and quit answer -902 to a word past + their grammar instead of acting on it: worker_list x no longer lists, + supervisor_reset all x no longer resets, and quit now no longer stops + the daemon. +- A tab at the end of a command is trimmed like a space: a command + ending in a carriage return and a tab no longer reaches the worker with + both. +- The end of a held gesture longer than 1024 bytes reaches the controller + whole, with its end word; before, it was cut short and the control stayed + held. +- The build needs mod-host's protocol headers of mod-host-protocol-devel + 0.10.6.f14a230-11 or newer (libmod-host-protocol-dev 0.1.0-5 or newer), the + first with param_info, remote_pages and track_info. +- Fuzz harnesses for the command parser and the reply stream (#144). + * Sat Oct 24 2026 Pau Aliagas - 0.9.1-1 - The preset schema, protocol/preset.schema.json, is installed in /usr/share/plugin-hostd with the two other schemas, in the development diff --git a/src/main.c b/src/main.c index d8351f1..06c39fa 100644 --- a/src/main.c +++ b/src/main.c @@ -35,7 +35,7 @@ #include "verbs.h" #ifndef VERSION -#define VERSION "0.9.1" +#define VERSION "0.9.2" #endif #define SOCKET_DEFAULT_PORT PHD_DEFAULT_COMMAND_PORT