lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <10708db9327a6db3e8cdd9639504923e6629ae85.1608238451.git.zanussi@kernel.org>
Date:   Thu, 17 Dec 2020 15:14:29 -0600
From:   Tom Zanussi <zanussi@...nel.org>
To:     rostedt@...dmis.org, axelrasmussen@...gle.com
Cc:     mhiramat@...nel.org, linux-kernel@...r.kernel.org
Subject: [PATCH v4 4/5] tracing: Add a backward-compatibility check for synthetic event creation

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.

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

diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c
index 2a9c8bf74bb2..6bff54ed31ce 100644
--- a/kernel/trace/trace_events_synth.c
+++ b/kernel/trace/trace_events_synth.c
@@ -1373,6 +1373,245 @@ 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, consumed = 0, n_fields = 0, semis_added = 0;
+	char *name, **argv, **save_argv;
+	int ret = -EINVAL;
+	struct seq_buf s;
+	bool added_semi;
+	char *buf;
+
+	argc = 0;
+
+	argv = argv_split(GFP_KERNEL, raw_command, &argc);
+	if (!argv)
+		return NULL;
+
+	if (!argc)
+		goto out;
+
+	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 out;
+	}
+
+	if (WARN_ON_ONCE(!seq_buf_buffer_left(&s)))
+		goto err;
+
+	buf[s.len] = '\0';
+ out:
+	argv_free(save_argv);
+
+	return buf;
+ err:
+	kfree(buf);
+	buf = ERR_PTR(ret);
+
+	goto out;
+}
+
+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 +1645,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 +1685,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 +2233,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 +2241,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 +2269,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

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ