Commit message: tracing: Check for no filter when processing event filters

The syzkaller detected a out-of-bounds issue with the events filter code,
specifically here:

	prog[N].pred = NULL;					/* #13 */
	prog[N].target = 1;		/* TRUE */
	prog[N+1].pred = NULL;
	prog[N+1].target = 0;		/* FALSE */
->	prog[N-1].target = N;
	prog[N-1].when_to_branch = false;

As that's the first reference to a "N-1" index, it appears that the code got
here with N = 0, which means the filter parser found no filter to parse
(which shouldn't ever happen, but apparently it did).

Add a new error to the parsing code that will check to make sure that N is
not zero before going into this part of the code. If N = 0, then -EINVAL is
returned, and a error message is added to the filter.

Cc: stable@vger.kernel.org
Fixes: 80765597bc587 ("tracing: Rewrite filter logic to be simpler and faster")
Reported-by: air icy <icytxw@gmail.com>
bugzilla url: https://bugzilla.kernel.org/show_bug.cgi?id=200019
Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
CVE: CVE-2018-12714
CWE: CWE-787
Vulnerability description: An issue was discovered in the Linux kernel through 4.17.2. The filter parsing in kernel/trace/trace_events_filter.c could be called with no filter, which is an N=0 case when it expected at least one line to have been read, thus making the N-1 index invalid. This allows attackers to cause a denial of service (slab out-of-bounds write) or possibly have unspecified other impact via crafted perf_event_open and mmap system calls.

Project: linux
File: trace_events_filter.c

static filter_pred_fn_t select_comparison_fn(enum filter_op_ids op,
					    int field_size, int field_is_signed)
{
	filter_pred_fn_t fn = NULL;
	int pred_func_index = -1;

	switch (op) {
	case OP_EQ:
	case OP_NE:
		break;
	default:
		if (WARN_ON_ONCE(op < PRED_FUNC_START))
			return NULL;
		pred_func_index = op - PRED_FUNC_START;
		if (WARN_ON_ONCE(pred_func_index > PRED_FUNC_MAX))
			return NULL;
	}

	switch (field_size) {
	case 8:
		if (pred_func_index < 0)
			fn = filter_pred_64;
		else if (field_is_signed)
			fn = pred_funcs_s64[pred_func_index];
		else
			fn = pred_funcs_u64[pred_func_index];
		break;
	case 4:
		if (pred_func_index < 0)
			fn = filter_pred_32;
		else if (field_is_signed)
			fn = pred_funcs_s32[pred_func_index];
		else
			fn = pred_funcs_u32[pred_func_index];
		break;
	case 2:
		if (pred_func_index < 0)
			fn = filter_pred_16;
		else if (field_is_signed)
			fn = pred_funcs_s16[pred_func_index];
		else
			fn = pred_funcs_u16[pred_func_index];
		break;
	case 1:
		if (pred_func_index < 0)
			fn = filter_pred_8;
		else if (field_is_signed)
			fn = pred_funcs_s8[pred_func_index];
		else
			fn = pred_funcs_u8[pred_func_index];
		break;
	}

	return fn;
}

Project: linux
File: trace_events_filter.c

static int create_filter(struct trace_event_call *call,
			 char *filter_string, bool set_str,
			 struct event_filter **filterp)
{
	struct filter_parse_error *pe = NULL;
	int err;

	err = create_filter_start(filter_string, set_str, &pe, filterp);
	if (err)
		return err;

	err = process_preds(call, filter_string, *filterp, pe);
	if (err && set_str)
		append_filter_err(pe, *filterp);

	return err;
}

Project: linux
File: trace_events_filter.c

static void filter_build_regex(struct filter_pred *pred)
{
	struct regex *r = &pred->regex;
	char *search;
	enum regex_type type = MATCH_FULL;

	if (pred->op == OP_GLOB) {
		type = filter_parse_regex(r->pattern, r->len, &search, &pred->not);
		r->len = strlen(search);
		memmove(r->pattern, search, r->len+1);
	}

	switch (type) {
	case MATCH_FULL:
		r->match = regex_match_full;
		break;
	case MATCH_FRONT_ONLY:
		r->match = regex_match_front;
		break;
	case MATCH_MIDDLE_ONLY:
		r->match = regex_match_middle;
		break;
	case MATCH_END_ONLY:
		r->match = regex_match_end;
		break;
	case MATCH_GLOB:
		r->match = regex_match_glob;
		break;
	}
}

Project: linux
File: trace_events_filter.c

static int parse_pred(const char *str, void *data,
		      int pos, struct filter_parse_error *pe,
		      struct filter_pred **pred_ptr)
{
	struct trace_event_call *call = data;
	struct ftrace_event_field *field;
	struct filter_pred *pred = NULL;
	char num_buf[24];	/* Big enough to hold an address */
	char *field_name;
	char q;
	u64 val;
	int len;
	int ret;
	int op;
	int s;
	int i = 0;

	/* First find the field to associate to */
	while (isspace(str[i]))
		i++;
	s = i;

	while (isalnum(str[i]) || str[i] == '_')
		i++;

	len = i - s;

	if (!len)
		return -1;

	field_name = kmemdup_nul(str + s, len, GFP_KERNEL);
	if (!field_name)
		return -ENOMEM;

	/* Make sure that the field exists */

	field = trace_find_event_field(call, field_name);
	kfree(field_name);
	if (!field) {
		parse_error(pe, FILT_ERR_FIELD_NOT_FOUND, pos + i);
		return -EINVAL;
	}

	while (isspace(str[i]))
		i++;

	/* Make sure this op is supported */
	for (op = 0; ops[op]; op++) {
		/* This is why '<=' must come before '<' in ops[] */
		if (strncmp(str + i, ops[op], strlen(ops[op])) == 0)
			break;
	}

	if (!ops[op]) {
		parse_error(pe, FILT_ERR_INVALID_OP, pos + i);
		goto err_free;
	}

	i += strlen(ops[op]);

	while (isspace(str[i]))
		i++;

	s = i;

	pred = kzalloc(sizeof(*pred), GFP_KERNEL);
	if (!pred)
		return -ENOMEM;

	pred->field = field;
	pred->offset = field->offset;
	pred->op = op;

	if (ftrace_event_is_function(call)) {
		/*
		 * Perf does things different with function events.
		 * It only allows an "ip" field, and expects a string.
		 * But the string does not need to be surrounded by quotes.
		 * If it is a string, the assigned function as a nop,
		 * (perf doesn't use it) and grab everything.
		 */
		if (strcmp(field->name, "ip") != 0) {
			 parse_error(pe, FILT_ERR_IP_FIELD_ONLY, pos + i);
			 goto err_free;
		 }
		 pred->fn = filter_pred_none;

		 /*
		  * Quotes are not required, but if they exist then we need
		  * to read them till we hit a matching one.
		  */
		 if (str[i] == '\'' || str[i] == '"')
			 q = str[i];
		 else
			 q = 0;

		 for (i++; str[i]; i++) {
			 if (q && str[i] == q)
				 break;
			 if (!q && (str[i] == ')' || str[i] == '&' ||
				    str[i] == '|'))
				 break;
		 }
		 /* Skip quotes */
		 if (q)
			 s++;
		len = i - s;
		if (len >= MAX_FILTER_STR_VAL) {
			parse_error(pe, FILT_ERR_OPERAND_TOO_LONG, pos + i);
			goto err_free;
		}

		pred->regex.len = len;
		strncpy(pred->regex.pattern, str + s, len);
		pred->regex.pattern[len] = 0;

	/* This is either a string, or an integer */
	} else if (str[i] == '\'' || str[i] == '"') {
		char q = str[i];

		/* Make sure the op is OK for strings */
		switch (op) {
		case OP_NE:
			pred->not = 1;
			/* Fall through */
		case OP_GLOB:
		case OP_EQ:
			break;
		default:
			parse_error(pe, FILT_ERR_ILLEGAL_FIELD_OP, pos + i);
			goto err_free;
		}

		/* Make sure the field is OK for strings */
		if (!is_string_field(field)) {
			parse_error(pe, FILT_ERR_EXPECT_DIGIT, pos + i);
			goto err_free;
		}

		for (i++; str[i]; i++) {
			if (str[i] == q)
				break;
		}
		if (!str[i]) {
			parse_error(pe, FILT_ERR_MISSING_QUOTE, pos + i);
			goto err_free;
		}

		/* Skip quotes */
		s++;
		len = i - s;
		if (len >= MAX_FILTER_STR_VAL) {
			parse_error(pe, FILT_ERR_OPERAND_TOO_LONG, pos + i);
			goto err_free;
		}

		pred->regex.len = len;
		strncpy(pred->regex.pattern, str + s, len);
		pred->regex.pattern[len] = 0;

		filter_build_regex(pred);

		if (field->filter_type == FILTER_COMM) {
			pred->fn = filter_pred_comm;

		} else if (field->filter_type == FILTER_STATIC_STRING) {
			pred->fn = filter_pred_string;
			pred->regex.field_len = field->size;

		} else if (field->filter_type == FILTER_DYN_STRING)
			pred->fn = filter_pred_strloc;
		else
			pred->fn = filter_pred_pchar;
		/* go past the last quote */
		i++;

	} else if (isdigit(str[i])) {

		/* Make sure the field is not a string */
		if (is_string_field(field)) {
			parse_error(pe, FILT_ERR_EXPECT_STRING, pos + i);
			goto err_free;
		}

		if (op == OP_GLOB) {
			parse_error(pe, FILT_ERR_ILLEGAL_FIELD_OP, pos + i);
			goto err_free;
		}

		/* We allow 0xDEADBEEF */
		while (isalnum(str[i]))
			i++;

		len = i - s;
		/* 0xfeedfacedeadbeef is 18 chars max */
		if (len >= sizeof(num_buf)) {
			parse_error(pe, FILT_ERR_OPERAND_TOO_LONG, pos + i);
			goto err_free;
		}

		strncpy(num_buf, str + s, len);
		num_buf[len] = 0;

		/* Make sure it is a value */
		if (field->is_signed)
			ret = kstrtoll(num_buf, 0, &val);
		else
			ret = kstrtoull(num_buf, 0, &val);
		if (ret) {
			parse_error(pe, FILT_ERR_ILLEGAL_INTVAL, pos + s);
			goto err_free;
		}

		pred->val = val;

		if (field->filter_type == FILTER_CPU)
			pred->fn = filter_pred_cpu;
		else {
			pred->fn = select_comparison_fn(pred->op, field->size,
							field->is_signed);
			if (pred->op == OP_NE)
				pred->not = 1;
		}

	} else {
		parse_error(pe, FILT_ERR_INVALID_VALUE, pos + i);
		goto err_free;
	}

	*pred_ptr = pred;
	return i;

err_free:
	kfree(pred);
	return -EINVAL;
}

Project: linux
File: trace_events_filter.c

int filter_match_preds(struct event_filter *filter, void *rec)
{
	struct prog_entry *prog;
	int i;

	/* no filter is considered a match */
	if (!filter)
		return 1;

	prog = rcu_dereference_sched(filter->prog);
	if (!prog)
		return 1;

	for (i = 0; prog[i].pred; i++) {
		struct filter_pred *pred = prog[i].pred;
		int match = pred->fn(pred, rec);
		if (match == prog[i].when_to_branch)
			i = prog[i].target;
	}
	return prog[i].target;
}

Project: linux
File: trace_events_filter.c

static int ftrace_function_set_filter(struct perf_event *event,
				      struct event_filter *filter)
{
	struct prog_entry *prog = rcu_dereference_protected(filter->prog,
						lockdep_is_held(&event_mutex));
	struct function_filter_data data = {
		.first_filter  = 1,
		.first_notrace = 1,
		.ops           = &event->ftrace_ops,
	};
	int i;

	for (i = 0; prog[i].pred; i++) {
		struct filter_pred *pred = prog[i].pred;

		if (!is_or(prog, i))
			return -EINVAL;

		if (ftrace_function_set_filter_pred(pred, &data) < 0)
			return -EINVAL;
	}
	return 0;
}

Project: linux
File: trace_events_filter.c

static int filter_pred_cpu(struct filter_pred *pred, void *event)
{
	int cpu, cmp;

	cpu = raw_smp_processor_id();
	cmp = pred->val;

	switch (pred->op) {
	case OP_EQ:
		return cpu == cmp;
	case OP_NE:
		return cpu != cmp;
	case OP_LT:
		return cpu < cmp;
	case OP_LE:
		return cpu <= cmp;
	case OP_GT:
		return cpu > cmp;
	case OP_GE:
		return cpu >= cmp;
	default:
		return 0;
	}
}

Project: linux
File: trace_events_filter.c

static int process_preds(struct trace_event_call *call,
			 const char *filter_string,
			 struct event_filter *filter,
			 struct filter_parse_error *pe)
{
	struct prog_entry *prog;
	int nr_parens;
	int nr_preds;
	int index;
	int ret;

	ret = calc_stack(filter_string, &nr_parens, &nr_preds, &index);
	if (ret < 0) {
		switch (ret) {
		case MISSING_QUOTE:
			parse_error(pe, FILT_ERR_MISSING_QUOTE, index);
			break;
		case TOO_MANY_OPEN:
			parse_error(pe, FILT_ERR_TOO_MANY_OPEN, index);
			break;
		default:
			parse_error(pe, FILT_ERR_TOO_MANY_CLOSE, index);
		}
		return ret;
	}

	if (!nr_preds)
		return -EINVAL;

	prog = predicate_parse(filter_string, nr_parens, nr_preds,
			       parse_pred, call, pe);
	if (IS_ERR(prog))
		return PTR_ERR(prog);

	rcu_assign_pointer(filter->prog, prog);
	return 0;
}

Project: linux
File: trace_events_filter.c

static inline void __free_subsystem_filter(struct trace_event_file *file)
{
	__free_filter(file->filter);
	file->filter = NULL;
}

Project: linux
File: trace_events_filter.c

ftrace_function_filter_re(char *buf, int len, int *count)
{
	char *str, **re;

	str = kstrndup(buf, len, GFP_KERNEL);
	if (!str)
		return NULL;

	/*
	 * The argv_split function takes white space
	 * as a separator, so convert ',' into spaces.
	 */
	strreplace(str, ',', ' ');

	re = argv_split(GFP_KERNEL, str, count);
	kfree(str);
	return re;
}

Project: linux
File: trace_events_filter.c

predicate_parse(const char *str, int nr_parens, int nr_preds,
		parse_pred_fn parse_pred, void *data,
		struct filter_parse_error *pe)
{
	struct prog_entry *prog_stack;
	struct prog_entry *prog;
	const char *ptr = str;
	char *inverts = NULL;
	int *op_stack;
	int *top;
	int invert = 0;
	int ret = -ENOMEM;
	int len;
	int N = 0;
	int i;

	nr_preds += 2; /* For TRUE and FALSE */

	op_stack = kmalloc_array(nr_parens, sizeof(*op_stack), GFP_KERNEL);
	if (!op_stack)
		return ERR_PTR(-ENOMEM);
	prog_stack = kmalloc_array(nr_preds, sizeof(*prog_stack), GFP_KERNEL);
	if (!prog_stack) {
		parse_error(pe, -ENOMEM, 0);
		goto out_free;
	}
	inverts = kmalloc_array(nr_preds, sizeof(*inverts), GFP_KERNEL);
	if (!inverts) {
		parse_error(pe, -ENOMEM, 0);
		goto out_free;
	}

	top = op_stack;
	prog = prog_stack;
	*top = 0;

	/* First pass */
	while (*ptr) {						/* #1 */
		const char *next = ptr++;

		if (isspace(*next))
			continue;

		switch (*next) {
		case '(':					/* #2 */
			if (top - op_stack > nr_parens)
				return ERR_PTR(-EINVAL);
			*(++top) = invert;
			continue;
		case '!':					/* #3 */
			if (!is_not(next))
				break;
			invert = !invert;
			continue;
		}

		if (N >= nr_preds) {
			parse_error(pe, FILT_ERR_TOO_MANY_PREDS, next - str);
			goto out_free;
		}

		inverts[N] = invert;				/* #4 */
		prog[N].target = N-1;

		len = parse_pred(next, data, ptr - str, pe, &prog[N].pred);
		if (len < 0) {
			ret = len;
			goto out_free;
		}
		ptr = next + len;

		N++;

		ret = -1;
		while (1) {					/* #5 */
			next = ptr++;
			if (isspace(*next))
				continue;

			switch (*next) {
			case ')':
			case '\0':
				break;
			case '&':
			case '|':
				if (next[1] == next[0]) {
					ptr++;
					break;
				}
			default:
				parse_error(pe, FILT_ERR_TOO_MANY_PREDS,
					    next - str);
				goto out_free;
			}

			invert = *top & INVERT;

			if (*top & PROCESS_AND) {		/* #7 */
				update_preds(prog, N - 1, invert);
				*top &= ~PROCESS_AND;
			}
			if (*next == '&') {			/* #8 */
				*top |= PROCESS_AND;
				break;
			}
			if (*top & PROCESS_OR) {		/* #9 */
				update_preds(prog, N - 1, !invert);
				*top &= ~PROCESS_OR;
			}
			if (*next == '|') {			/* #10 */
				*top |= PROCESS_OR;
				break;
			}
			if (!*next)				/* #11 */
				goto out;

			if (top == op_stack) {
				ret = -1;
				/* Too few '(' */
				parse_error(pe, FILT_ERR_TOO_MANY_CLOSE, ptr - str);
				goto out_free;
			}
			top--;					/* #12 */
		}
	}
 out:
	if (top != op_stack) {
		/* Too many '(' */
		parse_error(pe, FILT_ERR_TOO_MANY_OPEN, ptr - str);
		goto out_free;
	}

	if (!N) {
		/* No program? */
		ret = -EINVAL;
		parse_error(pe, FILT_ERR_NO_FILTER, ptr - str);
		goto out_free;
	}

	prog[N].pred = NULL;					/* #13 */
	prog[N].target = 1;		/* TRUE */
	prog[N+1].pred = NULL;
	prog[N+1].target = 0;		/* FALSE */
	prog[N-1].target = N;
	prog[N-1].when_to_branch = false;

	/* Second Pass */
	for (i = N-1 ; i--; ) {
		int target = prog[i].target;
		if (prog[i].when_to_branch == prog[target].when_to_branch)
			prog[i].target = prog[target].target;
	}

	/* Third Pass */
	for (i = 0; i < N; i++) {
		invert = inverts[i] ^ prog[i].when_to_branch;
		prog[i].when_to_branch = invert;
		/* Make sure the program always moves forward */
		if (WARN_ON(prog[i].target <= i)) {
			ret = -EINVAL;
			goto out_free;
		}
	}

	return prog;
out_free:
	kfree(op_stack);
	kfree(prog_stack);
	kfree(inverts);
	return ERR_PTR(ret);
}

Project: linux
File: trace_events_filter.c

static int filter_pred_none(struct filter_pred *pred, void *event)
{
	return 0;
}

Project: linux
File: trace_events_filter.c

static int filter_pred_strloc(struct filter_pred *pred, void *event)
{
	u32 str_item = *(u32 *)(event + pred->offset);
	int str_loc = str_item & 0xffff;
	int str_len = str_item >> 16;
	char *addr = (char *)(event + str_loc);
	int cmp, match;

	cmp = pred->regex.match(addr, &pred->regex, str_len);

	match = cmp ^ pred->not;

	return match;
}

Project: linux
File: trace_events_filter.c

static void free_prog(struct event_filter *filter)
{
	struct prog_entry *prog;
	int i;

	prog = rcu_access_pointer(filter->prog);
	if (!prog)
		return;

	for (i = 0; prog[i].pred; i++)
		kfree(prog[i].pred);
	kfree(prog);
}

Project: linux
File: trace_events_filter.c

static inline struct event_filter *event_filter(struct trace_event_file *file)
{
	return file->filter;
}

Project: linux
File: trace_events_filter.c

static void remove_filter_string(struct event_filter *filter)
{
	if (!filter)
		return;

	kfree(filter->filter_string);
	filter->filter_string = NULL;
}

Project: linux
File: trace_events_filter.c

static inline void event_clear_filter(struct trace_event_file *file)
{
	RCU_INIT_POINTER(file->filter, NULL);
}

Project: linux
File: trace_events_filter.c

static void parse_error(struct filter_parse_error *pe, int err, int pos)
{
	pe->lasterr = err;
	pe->lasterr_pos = pos;
}

Project: linux
File: trace_events_filter.c

int apply_subsystem_event_filter(struct trace_subsystem_dir *dir,
				 char *filter_string)
{
	struct event_subsystem *system = dir->subsystem;
	struct trace_array *tr = dir->tr;
	struct event_filter *filter = NULL;
	int err = 0;

	mutex_lock(&event_mutex);

	/* Make sure the system still has events */
	if (!dir->nr_events) {
		err = -ENODEV;
		goto out_unlock;
	}

	if (!strcmp(strstrip(filter_string), "0")) {
		filter_free_subsystem_preds(dir, tr);
		remove_filter_string(system->filter);
		filter = system->filter;
		system->filter = NULL;
		/* Ensure all filters are no longer used */
		synchronize_sched();
		filter_free_subsystem_filters(dir, tr);
		__free_filter(filter);
		goto out_unlock;
	}

	err = create_system_filter(dir, tr, filter_string, &filter);
	if (filter) {
		/*
		 * No event actually uses the system filter
		 * we can free it without synchronize_sched().
		 */
		__free_filter(system->filter);
		system->filter = filter;
	}
out_unlock:
	mutex_unlock(&event_mutex);

	return err;
}

Project: linux
File: trace_events_filter.c

void free_event_filter(struct event_filter *filter)
{
	__free_filter(filter);
}

Project: linux
File: trace_events_filter.c

static __init int ftrace_test_event_filter(void)
{
	int i;

	printk(KERN_INFO "Testing ftrace filter: ");

	for (i = 0; i < DATA_CNT; i++) {
		struct event_filter *filter = NULL;
		struct test_filter_data_t *d = &test_filter_data[i];
		int err;

		err = create_filter(&event_ftrace_test_filter, d->filter,
				    false, &filter);
		if (err) {
			printk(KERN_INFO
			       "Failed to get filter for '%s', err %d\n",
			       d->filter, err);
			__free_filter(filter);
			break;
		}

		/* Needed to dereference filter->prog */
		mutex_lock(&event_mutex);
		/*
		 * The preemption disabling is not really needed for self
		 * tests, but the rcu dereference will complain without it.
		 */
		preempt_disable();
		if (*d->not_visited)
			update_pred_fn(filter, d->not_visited);

		test_pred_visited = 0;
		err = filter_match_preds(filter, &d->rec);
		preempt_enable();

		mutex_unlock(&event_mutex);

		__free_filter(filter);

		if (test_pred_visited) {
			printk(KERN_INFO
			       "Failed, unwanted pred visited for filter %s\n",
			       d->filter);
			break;
		}

		if (err != d->match) {
			printk(KERN_INFO
			       "Failed to match filter '%s', expected %d\n",
			       d->filter, d->match);
			break;
		}
	}

	if (i == DATA_CNT)
		printk(KERN_CONT "OK\n");

	return 0;
}

Project: linux
File: trace_events_filter.c

void print_subsystem_event_filter(struct event_subsystem *system,
				  struct trace_seq *s)
{
	struct event_filter *filter;

	mutex_lock(&event_mutex);
	filter = system->filter;
	if (filter && filter->filter_string)
		trace_seq_printf(s, "%s\n", filter->filter_string);
	else
		trace_seq_puts(s, DEFAULT_SYS_FILTER_MESSAGE "\n");
	mutex_unlock(&event_mutex);
}

Project: linux
File: trace_events_filter.c

static int ftrace_function_set_filter(struct perf_event *event,
				      struct event_filter *filter)
{
	return -ENODEV;
}

Project: linux
File: trace_events_filter.c

static int process_system_preds(struct trace_subsystem_dir *dir,
				struct trace_array *tr,
				struct filter_parse_error *pe,
				char *filter_string)
{
	struct trace_event_file *file;
	struct filter_list *filter_item;
	struct event_filter *filter = NULL;
	struct filter_list *tmp;
	LIST_HEAD(filter_list);
	bool fail = true;
	int err;

	list_for_each_entry(file, &tr->events, list) {

		if (file->system != dir)
			continue;

		filter = kzalloc(sizeof(*filter), GFP_KERNEL);
		if (!filter)
			goto fail_mem;

		filter->filter_string = kstrdup(filter_string, GFP_KERNEL);
		if (!filter->filter_string)
			goto fail_mem;

		err = process_preds(file->event_call, filter_string, filter, pe);
		if (err) {
			filter_disable(file);
			parse_error(pe, FILT_ERR_BAD_SUBSYS_FILTER, 0);
			append_filter_err(pe, filter);
		} else
			event_set_filtered_flag(file);


		filter_item = kzalloc(sizeof(*filter_item), GFP_KERNEL);
		if (!filter_item)
			goto fail_mem;

		list_add_tail(&filter_item->list, &filter_list);
		/*
		 * Regardless of if this returned an error, we still
		 * replace the filter for the call.
		 */
		filter_item->filter = event_filter(file);
		event_set_filter(file, filter);
		filter = NULL;

		fail = false;
	}

	if (fail)
		goto fail;

	/*
	 * The calls can still be using the old filters.
	 * Do a synchronize_sched() to ensure all calls are
	 * done with them before we free them.
	 */
	synchronize_sched();
	list_for_each_entry_safe(filter_item, tmp, &filter_list, list) {
		__free_filter(filter_item->filter);
		list_del(&filter_item->list);
		kfree(filter_item);
	}
	return 0;
 fail:
	/* No call succeeded */
	list_for_each_entry_safe(filter_item, tmp, &filter_list, list) {
		list_del(&filter_item->list);
		kfree(filter_item);
	}
	parse_error(pe, FILT_ERR_BAD_SUBSYS_FILTER, 0);
	return -EINVAL;
 fail_mem:
	kfree(filter);
	/* If any call succeeded, we still need to sync */
	if (!fail)
		synchronize_sched();
	list_for_each_entry_safe(filter_item, tmp, &filter_list, list) {
		__free_filter(filter_item->filter);
		list_del(&filter_item->list);
		kfree(filter_item);
	}
	return -ENOMEM;
}

Project: linux
File: trace_events_filter.c

static void filter_disable(struct trace_event_file *file)
{
	unsigned long old_flags = file->flags;

	file->flags &= ~EVENT_FILE_FL_FILTERED;

	if (old_flags != file->flags)
		trace_buffered_event_disable();
}

Project: linux
File: trace_events_filter.c

static int regex_match_glob(char *str, struct regex *r, int len __maybe_unused)
{
	if (glob_match(r->pattern, str))
		return 1;
	return 0;
}

Project: linux
File: trace_events_filter.c

static void __free_filter(struct event_filter *filter)
{
	if (!filter)
		return;

	free_prog(filter);
	kfree(filter->filter_string);
	kfree(filter);
}

Project: linux
File: trace_events_filter.c

int ftrace_profile_set_filter(struct perf_event *event, int event_id,
			      char *filter_str)
{
	int err;
	struct event_filter *filter = NULL;
	struct trace_event_call *call;

	mutex_lock(&event_mutex);

	call = event->tp_event;

	err = -EINVAL;
	if (!call)
		goto out_unlock;

	err = -EEXIST;
	if (event->filter)
		goto out_unlock;

	err = create_filter(call, filter_str, false, &filter);
	if (err)
		goto free_filter;

	if (ftrace_event_is_function(call))
		err = ftrace_function_set_filter(event, filter);
	else
		event->filter = filter;

free_filter:
	if (err || ftrace_event_is_function(call))
		__free_filter(filter);

out_unlock:
	mutex_unlock(&event_mutex);

	return err;
}

Project: linux
File: trace_events_filter.c

static inline void __remove_filter(struct trace_event_file *file)
{
	filter_disable(file);
	remove_filter_string(file->filter);
}

Project: linux
File: trace_events_filter.c

static void append_filter_err(struct filter_parse_error *pe,
			      struct event_filter *filter)
{
	struct trace_seq *s;
	int pos = pe->lasterr_pos;
	char *buf;
	int len;

	if (WARN_ON(!filter->filter_string))
		return;

	s = kmalloc(sizeof(*s), GFP_KERNEL);
	if (!s)
		return;
	trace_seq_init(s);

	len = strlen(filter->filter_string);
	if (pos > len)
		pos = len;

	/* indexing is off by one */
	if (pos)
		pos++;

	trace_seq_puts(s, filter->filter_string);
	if (pe->lasterr > 0) {
		trace_seq_printf(s, "\n%*s", pos, "^");
		trace_seq_printf(s, "\nparse_error: %s\n", err_text[pe->lasterr]);
	} else {
		trace_seq_printf(s, "\nError: (%d)\n", pe->lasterr);
	}
	trace_seq_putc(s, 0);
	buf = kmemdup_nul(s->buffer, s->seq.len, GFP_KERNEL);
	if (buf) {
		kfree(filter->filter_string);
		filter->filter_string = buf;
	}
	kfree(s);
}

Project: linux
File: trace_events_filter.c

static int create_system_filter(struct trace_subsystem_dir *dir,
				struct trace_array *tr,
				char *filter_str, struct event_filter **filterp)
{
	struct filter_parse_error *pe = NULL;
	int err;

	err = create_filter_start(filter_str, true, &pe, filterp);
	if (!err) {
		err = process_system_preds(dir, tr, pe, filter_str);
		if (!err) {
			/* System filters just show a default message */
			kfree((*filterp)->filter_string);
			(*filterp)->filter_string = NULL;
		} else {
			append_filter_err(pe, *filterp);
		}
	}
	create_filter_finish(pe);

	return err;
}

Project: linux
File: trace_events_filter.c

static int calc_stack(const char *str, int *parens, int *preds, int *err)
{
	bool is_pred = false;
	int nr_preds = 0;
	int open = 1; /* Count the expression as "(E)" */
	int last_quote = 0;
	int max_open = 1;
	int quote = 0;
	int i;

	*err = 0;

	for (i = 0; str[i]; i++) {
		if (isspace(str[i]))
			continue;
		if (quote) {
			if (str[i] == quote)
			       quote = 0;
			continue;
		}

		switch (str[i]) {
		case '\'':
		case '"':
			quote = str[i];
			last_quote = i;
			break;
		case '|':
		case '&':
			if (str[i+1] != str[i])
				break;
			is_pred = false;
			continue;
		case '(':
			is_pred = false;
			open++;
			if (open > max_open)
				max_open = open;
			continue;
		case ')':
			is_pred = false;
			if (open == 1) {
				*err = i;
				return TOO_MANY_CLOSE;
			}
			open--;
			continue;
		}
		if (!is_pred) {
			nr_preds++;
			is_pred = true;
		}
	}

	if (quote) {
		*err = last_quote;
		return MISSING_QUOTE;
	}

	if (open != 1) {
		int level = open;

		/* find the bad open */
		for (i--; i; i--) {
			if (quote) {
				if (str[i] == quote)
					quote = 0;
				continue;
			}
			switch (str[i]) {
			case '(':
				if (level == open) {
					*err = i;
					return TOO_MANY_OPEN;
				}
				level--;
				break;
			case ')':
				level++;
				break;
			case '\'':
			case '"':
				quote = str[i];
				break;
			}
		}
		/* First character is the '(' with missing ')' */
		*err = 0;
		return TOO_MANY_OPEN;
	}

	/* Set the size of the required stacks */
	*parens = max_open;
	*preds = nr_preds;
	return 0;
}

Project: linux
File: trace_events_filter.c

static int ftrace_function_check_pred(struct filter_pred *pred)
{
	struct ftrace_event_field *field = pred->field;

	/*
	 * Check the predicate for function trace, verify:
	 *  - only '==' and '!=' is used
	 *  - the 'ip' field is used
	 */
	if ((pred->op != OP_EQ) && (pred->op != OP_NE))
		return -EINVAL;

	if (strcmp(field->name, "ip"))
		return -EINVAL;

	return 0;
}

Project: linux
File: trace_events_filter.c

static int ftrace_function_set_filter_pred(struct filter_pred *pred,
					   struct function_filter_data *data)
{
	int ret;

	/* Checking the node is valid for function trace. */
	ret = ftrace_function_check_pred(pred);
	if (ret)
		return ret;

	return __ftrace_function_set_filter(pred->op == OP_EQ,
					    pred->regex.pattern,
					    pred->regex.len,
					    data);
}

Project: linux
File: trace_events_filter.c

static int create_filter_start(char *filter_string, bool set_str,
			       struct filter_parse_error **pse,
			       struct event_filter **filterp)
{
	struct event_filter *filter;
	struct filter_parse_error *pe = NULL;
	int err = 0;

	if (WARN_ON_ONCE(*pse || *filterp))
		return -EINVAL;

	filter = kzalloc(sizeof(*filter), GFP_KERNEL);
	if (filter && set_str) {
		filter->filter_string = kstrdup(filter_string, GFP_KERNEL);
		if (!filter->filter_string)
			err = -ENOMEM;
	}

	pe = kzalloc(sizeof(*pe), GFP_KERNEL);

	if (!filter || !pe || err) {
		kfree(pe);
		__free_filter(filter);
		return -ENOMEM;
	}

	/* we're committed to creating a new filter */
	*filterp = filter;
	*pse = pe;

	return 0;
}

Project: linux
File: trace_events_filter.c

static int regex_match_middle(char *str, struct regex *r, int len)
{
	if (!len)
		return strstr(str, r->pattern) != NULL;

	return strnstr(str, r->pattern, len) != NULL;
}

Project: linux
File: trace_events_filter.c

event_no_set_filter_flag(struct trace_event_file *file)
{
	if (file->flags & EVENT_FILE_FL_NO_SET_FILTER)
		return true;

	return false;
}

Project: linux
File: trace_events_filter.c

int filter_assign_type(const char *type)
{
	if (strstr(type, "__data_loc") && strstr(type, "char"))
		return FILTER_DYN_STRING;

	if (strchr(type, '[') && strstr(type, "char"))
		return FILTER_STATIC_STRING;

	return FILTER_OTHER;
}

Project: linux
File: trace_events_filter.c

int apply_event_filter(struct trace_event_file *file, char *filter_string)
{
	struct trace_event_call *call = file->event_call;
	struct event_filter *filter = NULL;
	int err;

	if (!strcmp(strstrip(filter_string), "0")) {
		filter_disable(file);
		filter = event_filter(file);

		if (!filter)
			return 0;

		event_clear_filter(file);

		/* Make sure the filter is not being used */
		synchronize_sched();
		__free_filter(filter);

		return 0;
	}

	err = create_filter(call, filter_string, true, &filter);

	/*
	 * Always swap the call filter with the new filter
	 * even if there was an error. If there was an error
	 * in the filter, we disable the filter and show the error
	 * string
	 */
	if (filter) {
		struct event_filter *tmp;

		tmp = event_filter(file);
		if (!err)
			event_set_filtered_flag(file);
		else
			filter_disable(file);

		event_set_filter(file, filter);

		if (tmp) {
			/* Make sure the call is done with the filter */
			synchronize_sched();
			__free_filter(tmp);
		}
	}

	return err;
}

Project: linux
File: trace_events_filter.c

static int __ftrace_function_set_filter(int filter, char *buf, int len,
					struct function_filter_data *data)
{
	int i, re_cnt, ret = -EINVAL;
	int *reset;
	char **re;

	reset = filter ? &data->first_filter : &data->first_notrace;

	/*
	 * The 'ip' field could have multiple filters set, separated
	 * either by space or comma. We first cut the filter and apply
	 * all pieces separatelly.
	 */
	re = ftrace_function_filter_re(buf, len, &re_cnt);
	if (!re)
		return -EINVAL;

	for (i = 0; i < re_cnt; i++) {
		ret = ftrace_function_set_regexp(data->ops, filter, *reset,
						 re[i], strlen(re[i]));
		if (ret)
			break;

		if (*reset)
			*reset = 0;
	}

	argv_free(re);
	return ret;
}

Project: linux
File: trace_events_filter.c

void print_event_filter(struct trace_event_file *file, struct trace_seq *s)
{
	struct event_filter *filter = event_filter(file);

	if (filter && filter->filter_string)
		trace_seq_printf(s, "%s\n", filter->filter_string);
	else
		trace_seq_puts(s, "none\n");
}

Project: linux
File: trace_events_filter.c

event_clear_no_set_filter_flag(struct trace_event_file *file)
{
	file->flags &= ~EVENT_FILE_FL_NO_SET_FILTER;
}

Project: linux
File: trace_events_filter.c

static int filter_pred_pchar(struct filter_pred *pred, void *event)
{
	char **addr = (char **)(event + pred->offset);
	int cmp, match;
	int len = strlen(*addr) + 1;	/* including tailing '\0' */

	cmp = pred->regex.match(*addr, &pred->regex, len);

	match = cmp ^ pred->not;

	return match;
}

Project: linux
File: trace_events_filter.c

static int regex_match_front(char *str, struct regex *r, int len)
{
	if (len && len < r->len)
		return 0;

	return strncmp(str, r->pattern, r->len) == 0;
}

Project: linux
File: trace_events_filter.c

void ftrace_profile_free_filter(struct perf_event *event)
{
	struct event_filter *filter = event->filter;

	event->filter = NULL;
	__free_filter(filter);
}

Project: linux
File: trace_events_filter.c

static int test_pred_visited_fn(struct filter_pred *pred, void *event)
{
	struct ftrace_event_field *field = pred->field;

	test_pred_visited = 1;
	printk(KERN_INFO "\npred visited %s\n", field->name);
	return 1;
}

Project: linux
File: trace_events_filter.c

static void filter_free_subsystem_filters(struct trace_subsystem_dir *dir,
					  struct trace_array *tr)
{
	struct trace_event_file *file;

	list_for_each_entry(file, &tr->events, list) {
		if (file->system != dir)
			continue;
		__free_subsystem_filter(file);
	}
}

Project: linux
File: trace_events_filter.c

static int regex_match_end(char *str, struct regex *r, int len)
{
	int strlen = len - 1;

	if (strlen >= r->len &&
	    memcmp(str + strlen - r->len, r->pattern, r->len) == 0)
		return 1;
	return 0;
}

Project: linux
File: trace_events_filter.c

static void filter_free_subsystem_preds(struct trace_subsystem_dir *dir,
					struct trace_array *tr)
{
	struct trace_event_file *file;

	list_for_each_entry(file, &tr->events, list) {
		if (file->system != dir)
			continue;
		__remove_filter(file);
	}
}

Project: linux
File: trace_events_filter.c

static bool is_or(struct prog_entry *prog, int i)
{
	int target;

	/*
	 * Only "||" is allowed for function events, thus,
	 * all true branches should jump to true, and any
	 * false branch should jump to false.
	 */
	target = prog[i].target + 1;
	/* True and false have NULL preds (all prog entries should jump to one */
	if (prog[target].pred)
		return false;

	/* prog[target].target is 1 for TRUE, 0 for FALSE */
	return prog[i].when_to_branch == prog[target].target;
}

Project: linux
File: trace_events_filter.c

static inline void event_set_filtered_flag(struct trace_event_file *file)
{
	unsigned long old_flags = file->flags;

	file->flags |= EVENT_FILE_FL_FILTERED;

	if (old_flags != file->flags)
		trace_buffered_event_enable();
}

Project: linux
File: trace_events_filter.c

static int filter_pred_comm(struct filter_pred *pred, void *event)
{
	int cmp;

	cmp = pred->regex.match(current->comm, &pred->regex,
				TASK_COMM_LEN);
	return cmp ^ pred->not;
}

Project: linux
File: trace_events_filter.c

static bool is_not(const char *str)
{
	switch (str[1]) {
	case '=':
	case '~':
		return false;
	}
	return true;
}

Project: linux
File: trace_events_filter.c

int create_event_filter(struct trace_event_call *call,
			char *filter_str, bool set_str,
			struct event_filter **filterp)
{
	return create_filter(call, filter_str, set_str, filterp);
}

Project: linux
File: trace_events_filter.c

static inline void event_set_filter(struct trace_event_file *file,
				    struct event_filter *filter)
{
	rcu_assign_pointer(file->filter, filter);
}

Project: linux
File: trace_events_filter.c

enum regex_type filter_parse_regex(char *buff, int len, char **search, int *not)
{
	int type = MATCH_FULL;
	int i;

	if (buff[0] == '!') {
		*not = 1;
		buff++;
		len--;
	} else
		*not = 0;

	*search = buff;

	for (i = 0; i < len; i++) {
		if (buff[i] == '*') {
			if (!i) {
				type = MATCH_END_ONLY;
			} else if (i == len - 1) {
				if (type == MATCH_END_ONLY)
					type = MATCH_MIDDLE_ONLY;
				else
					type = MATCH_FRONT_ONLY;
				buff[i] = 0;
				break;
			} else {	/* pattern continues, use full glob */
				return MATCH_GLOB;
			}
		} else if (strchr("[?\\", buff[i])) {
			return MATCH_GLOB;
		}
	}
	if (buff[0] == '*')
		*search = buff + 1;

	return type;
}

Project: linux
File: trace_events_filter.c

static void create_filter_finish(struct filter_parse_error *pe)
{
	kfree(pe);
}

Project: linux
File: trace_events_filter.c

static int ftrace_function_set_regexp(struct ftrace_ops *ops, int filter,
				      int reset, char *re, int len)
{
	int ret;

	if (filter)
		ret = ftrace_set_filter(ops, re, len, reset);
	else
		ret = ftrace_set_notrace(ops, re, len, reset);

	return ret;
}

Project: linux
File: trace_events_filter.c

static void update_pred_fn(struct event_filter *filter, char *fields)
{
	struct prog_entry *prog = rcu_dereference_protected(filter->prog,
						lockdep_is_held(&event_mutex));
	int i;

	for (i = 0; prog[i].pred; i++) {
		struct filter_pred *pred = prog[i].pred;
		struct ftrace_event_field *field = pred->field;

		WARN_ON_ONCE(!pred->fn);

		if (!field) {
			WARN_ONCE(1, "all leafs should have field defined %d", i);
			continue;
		}

		if (!strchr(fields, *field->name))
			continue;

		pred->fn = test_pred_visited_fn;
	}
}

Project: linux
File: trace_events_filter.c

event_set_no_set_filter_flag(struct trace_event_file *file)
{
	file->flags |= EVENT_FILE_FL_NO_SET_FILTER;
}

Project: linux
File: trace_events_filter.c

static int regex_match_full(char *str, struct regex *r, int len)
{
	/* len of zero means str is dynamic and ends with '\0' */
	if (!len)
		return strcmp(str, r->pattern) == 0;

	return strncmp(str, r->pattern, len) == 0;
}

Project: linux
File: trace_events_filter.c

static void update_preds(struct prog_entry *prog, int N, int invert)
{
	int t, s;

	t = prog[N].target;
	s = prog[t].target;
	prog[t].when_to_branch = invert;
	prog[t].target = N;
	prog[N].target = s;
}

Project: linux
File: trace_events_filter.c

static int filter_pred_string(struct filter_pred *pred, void *event)
{
	char *addr = (char *)(event + pred->offset);
	int cmp, match;

	cmp = pred->regex.match(addr, &pred->regex, pred->regex.field_len);

	match = cmp ^ pred->not;

	return match;
}