[PATCH v5 4/5] tracing: Add a backward-compatibility check for synthetic event creation

From: Tom Zanussi
Date: Mon Dec 21 2020 - 16:45:32 EST


The synthetic event parsing rework requiring semicolons between
synthetic event fields. That requirement breaks existing users who
might already have used the old form, so this adds a pre-parsing pass
that adds semicolons between fields to any string missing them. If
none are required, the original string is used.

In the future, if/when new features are added, the requirement will be
that any string containing the new feature will be required to use
semicolons, and the audit_old_buffer() check can check for those and
avoid the pre-parsing semicolon pass altogether.

As it stands, the pre-parsing pass creates a new string with
semicolons only if one or more semicolons were actually needed and
only if no errors were found in pre-parsing. The assumption is that
the real parsing pass will find and flag any errors and the user
should see them in reference to the original unmodified string.

[ zanussi: Added missing variable initializations reported by Dan
Carpenter <dan.carpenter@xxxxxxxxxx> and kernel test robot
<lkp@xxxxxxxxx> ]

Signed-off-by: Tom Zanussi <zanussi@xxxxxxxxxx>
---
kernel/trace/trace_events_synth.c | 292 ++++++++++++++++++++++++++++--
1 file changed, 272 insertions(+), 20 deletions(-)

diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c
index 2a9c8bf74bb2..2aaa1a202beb 100644
--- a/kernel/trace/trace_events_synth.c
+++ b/kernel/trace/trace_events_synth.c
@@ -1373,6 +1373,243 @@ int synth_event_delete(const char *event_name)
}
EXPORT_SYMBOL_GPL(synth_event_delete);

+static int save_synth_field(int argc, char **argv, int *consumed,
+ struct seq_buf *s, bool *added_semi)
+{
+ const char *prefix = NULL, *field_name, *field_type = argv[0];
+ const char *save_field_type, *array, *next_tok;
+ int len, ret = -EINVAL;
+ struct seq_buf f;
+ ssize_t size;
+ char *tmp;
+
+ *added_semi = false;
+
+ if (field_type[0] == ';')
+ field_type++;
+
+ if (!strcmp(field_type, "unsigned")) {
+ if (argc < 3)
+ goto out;
+ prefix = "unsigned";
+ field_type = argv[1];
+ field_name = argv[2];
+ *consumed = 3;
+ } else {
+ field_type = argv[0];
+ field_name = argv[1];
+ *consumed = 2;
+ }
+
+ len = strlen(field_name);
+ array = strchr(field_name, '[');
+ if (array)
+ len -= strlen(array);
+ else if (field_name[len - 1] == ';')
+ len--;
+
+ tmp = kmemdup_nul(field_name, len, GFP_KERNEL);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ if (!is_good_name(tmp)) {
+ kfree(tmp);
+ goto out;
+ }
+
+ kfree(tmp);
+
+ save_field_type = field_type;
+ if (field_type[0] == ';')
+ field_type++;
+ len = strlen(field_type) + 1;
+
+ if (array)
+ len += strlen(array);
+
+ if (prefix)
+ len += strlen(prefix) + 1;
+
+ tmp = kzalloc(len, GFP_KERNEL);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ seq_buf_init(&f, tmp, len);
+ if (prefix) {
+ seq_buf_puts(&f, prefix);
+ seq_buf_putc(&f, ' ');
+ }
+ seq_buf_puts(&f, field_type);
+ if (array) {
+ seq_buf_puts(&f, array);
+ if (f.buffer[f.len - 1] == ';')
+ f.len--;
+ }
+ if (WARN_ON_ONCE(!seq_buf_buffer_left(&f))) {
+ kfree(tmp);
+ goto out;
+ }
+
+ f.buffer[f.len] = '\0';
+
+ field_type = save_field_type;
+
+ size = synth_field_size(tmp);
+ if (size < 0 || ((size == 0) && (!synth_field_is_string(tmp)))) {
+ kfree(tmp);
+ goto out;
+ }
+
+ kfree(tmp);
+
+ if (prefix) {
+ seq_buf_puts(s, prefix);
+ seq_buf_putc(s, ' ');
+ }
+ seq_buf_puts(s, field_type);
+ seq_buf_putc(s, ' ');
+ seq_buf_puts(s, field_name);
+ if (field_name[strlen(field_name) - 1] == ';')
+ seq_buf_putc(s, ' ');
+
+ if (*consumed < argc) {
+ next_tok = argv[*consumed];
+ if (field_name[strlen(field_name) - 1] != ';' &&
+ next_tok[0] != ';') {
+ seq_buf_puts(s, "; ");
+ *added_semi = true;
+ }
+ }
+
+ ret = 0;
+ out:
+ return ret;
+}
+
+static char *insert_semicolons(const char *raw_command)
+{
+ int i, argc = 0, consumed = 0, n_fields = 0, semis_added = 0;
+ char *name, **argv, **save_argv = NULL, *buf = NULL;
+ int ret = -EINVAL;
+ struct seq_buf s;
+ bool added_semi;
+
+ argv = argv_split(GFP_KERNEL, raw_command, &argc);
+ if (!argv)
+ return NULL;
+
+ if (!argc)
+ goto free;
+
+ name = argv[0];
+ save_argv = argv;
+ argv++;
+ argc--;
+
+ buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ seq_buf_init(&s, buf, MAX_DYNEVENT_CMD_LEN);
+
+ seq_buf_puts(&s, name);
+ seq_buf_putc(&s, ' ');
+
+ if (name[0] == '\0' || argc < 1)
+ goto err;
+
+ for (i = 0; i < argc - 1; i++) {
+ if (strcmp(argv[i], ";") == 0) {
+ seq_buf_puts(&s, " ; ");
+ continue;
+ }
+
+ if (n_fields == SYNTH_FIELDS_MAX)
+ goto err;
+
+ ret = save_synth_field(argc - i, &argv[i], &consumed,
+ &s, &added_semi);
+ if (ret)
+ goto err;
+
+ if (added_semi)
+ semis_added++;
+
+ i += consumed - 1;
+ }
+
+ if (i < argc && strcmp(argv[i], ";") != 0)
+ goto err;
+
+ if (!semis_added) {
+ kfree(buf);
+ buf = NULL;
+ goto free;
+ }
+
+ if (WARN_ON_ONCE(!seq_buf_buffer_left(&s)))
+ goto err;
+
+ buf[s.len] = '\0';
+ free:
+ if (save_argv)
+ argv_free(save_argv);
+
+ return buf;
+ err:
+ kfree(buf);
+ buf = ERR_PTR(ret);
+
+ goto free;
+}
+
+static bool audit_old_buffer(const char *cmd)
+{
+ /* as of now, every cmd is an old cmd */
+ return true;
+}
+
+/**
+ * get_parseable_cmd - Return a modifiable string for parsing
+ * @raw_command: The command to start with
+ *
+ * Create a cmd string that can be modified by the caller for command
+ * parsing purposes. If successful, the caller must free the command
+ * returned.
+ *
+ * The input string will first be checked to see whether or not it can
+ * be considered an 'old command' - a command that doesn't require
+ * semicolons between fields - for which backward compatibility must
+ * be maintained. If it can be considered an old command, a semicolon
+ * will be added between any two fields missing one. If no semicolons
+ * were required, or if the preparsing required for the pass
+ * encountered errors, a modifiable copy of the original string will
+ * be returned.
+ *
+ * Return: parseable cmd if successful, error or NULL string otherwise.
+ */
+static char *get_parseable_cmd(const char *raw_command)
+{
+ char *cmd = NULL;
+
+ if (audit_old_buffer(raw_command))
+ cmd = insert_semicolons(raw_command);
+
+ if (IS_ERR_OR_NULL(cmd)) {
+ cmd = kstrdup(raw_command, GFP_KERNEL);
+ if (!cmd)
+ cmd = ERR_PTR(-ENOMEM);
+ }
+
+ return cmd;
+}
+
static int check_command(const char *raw_command)
{
char **argv = NULL, *cmd, *saved_cmd, *name_and_field;
@@ -1406,28 +1643,33 @@ static int check_command(const char *raw_command)

static int create_or_delete_synth_event(const char *raw_command)
{
- char *name = NULL, *fields, *p;
+ char *name = NULL, *fields, *p, *cmd;
int ret = 0;

raw_command = skip_spaces(raw_command);
if (raw_command[0] == '\0')
return ret;

- last_cmd_set(raw_command);
+ cmd = get_parseable_cmd(raw_command);
+ if (IS_ERR(cmd))
+ return PTR_ERR(cmd);

- ret = check_command(raw_command);
+ last_cmd_set(cmd);
+
+ ret = check_command(cmd);
if (ret) {
synth_err(SYNTH_ERR_INVALID_CMD, 0);
- return ret;
+ goto free;
}

- p = strpbrk(raw_command, " \t");
+ p = strpbrk(cmd, " \t");
if (!p) {
synth_err(SYNTH_ERR_INVALID_CMD, 0);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free;
}

- name = kmemdup_nul(raw_command, p - raw_command, GFP_KERNEL);
+ name = kmemdup_nul(cmd, p - cmd, GFP_KERNEL);
if (!name)
return -ENOMEM;

@@ -1441,6 +1683,7 @@ static int create_or_delete_synth_event(const char *raw_command)
ret = __create_synth_event(name, fields);
free:
kfree(name);
+ kfree(cmd);

return ret;
}
@@ -1988,7 +2231,7 @@ EXPORT_SYMBOL_GPL(synth_event_trace_end);

static int create_synth_event(const char *raw_command)
{
- char *fields, *p;
+ char *fields, *p, *cmd;
const char *name;
int len, ret = 0;

@@ -1996,20 +2239,27 @@ static int create_synth_event(const char *raw_command)
if (raw_command[0] == '\0')
return ret;

- last_cmd_set(raw_command);
+ cmd = get_parseable_cmd(raw_command);
+ if (IS_ERR(cmd))
+ return PTR_ERR(cmd);
+
+ last_cmd_set(cmd);

- p = strpbrk(raw_command, " \t");
+ p = strpbrk(cmd, " \t");
if (!p) {
synth_err(SYNTH_ERR_INVALID_CMD, 0);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free;
}

fields = skip_spaces(p);

- name = raw_command;
+ name = cmd;

- if (name[0] != 's' || name[1] != ':')
- return -ECANCELED;
+ if (name[0] != 's' || name[1] != ':') {
+ ret = -ECANCELED;
+ goto free;
+ }
name += 2;

/* This interface accepts group name prefix */
@@ -2017,26 +2267,28 @@ static int create_synth_event(const char *raw_command)
len = str_has_prefix(name, SYNTH_SYSTEM "/");
if (len == 0) {
synth_err(SYNTH_ERR_INVALID_DYN_CMD, 0);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free;
}
name += len;
}

- len = name - raw_command;
+ len = name - cmd;

- ret = check_command(raw_command + len);
+ ret = check_command(cmd + len);
if (ret) {
synth_err(SYNTH_ERR_INVALID_CMD, 0);
- return ret;
+ goto free;
}

- name = kmemdup_nul(raw_command + len, p - raw_command - len, GFP_KERNEL);
+ name = kmemdup_nul(cmd + len, p - cmd - len, GFP_KERNEL);
if (!name)
return -ENOMEM;

ret = __create_synth_event(name, fields);
-
kfree(name);
+ free:
+ kfree(cmd);

return ret;
}
--
2.17.1