[PATCH tracing 1/3] tracing: support > 8 byte array filter predicates

From: Alan Maguire
Date: Tue Apr 25 2023 - 05:16:58 EST


For > 8 byte values, allow simple binary '==', '!=' predicates
where the user passes in a hex ASCII representation of the
desired value. This representation must match the field size
exactly, and a simple memory comparison between predicate and
actual values is carried out. This will allow predicates with
for example IPv6 addresses to be supported, such as filtering
on ::1

cd /sys/kernel/debug/tracing/events/tcp/tcp_receive_reset
echo "saddr_v6 == 0x00000000000000000000000000000001" > filter

Signed-off-by: Alan Maguire <alan.maguire@xxxxxxxxxx>
---
kernel/trace/trace_events_filter.c | 55 +++++++++++++++++++++++++++++-
1 file changed, 54 insertions(+), 1 deletion(-)

diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c
index 1dad64267878..4f4f53b7302d 100644
--- a/kernel/trace/trace_events_filter.c
+++ b/kernel/trace/trace_events_filter.c
@@ -67,6 +67,7 @@ enum filter_pred_fn {
FILTER_PRED_FN_FUNCTION,
FILTER_PRED_FN_,
FILTER_PRED_TEST_VISITED,
+ FILTER_PRED_FN_MEMCMP,
};

struct filter_pred {
@@ -622,8 +623,11 @@ predicate_parse(const char *str, int nr_parens, int nr_preds,
kfree(op_stack);
kfree(inverts);
if (prog_stack) {
- for (i = 0; prog_stack[i].pred; i++)
+ for (i = 0; prog_stack[i].pred; i++) {
+ if (prog_stack[i].pred->fn_num == FILTER_PRED_FN_MEMCMP)
+ kfree((u8 *)prog_stack[i].pred->val);
kfree(prog_stack[i].pred);
+ }
kfree(prog_stack);
}
return ERR_PTR(ret);
@@ -890,6 +894,14 @@ static int filter_pred_function(struct filter_pred *pred, void *event)
return pred->op == OP_EQ ? ret : !ret;
}

+static int filter_pred_memcmp(struct filter_pred *pred, void *event)
+{
+ u8 *mem = (u8 *)(event + pred->offset);
+ u8 *cmp = (u8 *)(pred->val);
+
+ return (memcmp(mem, cmp, pred->field->size) == 0) ^ pred->not;
+}
+
/*
* regex_match_foo - Basic regex callbacks
*
@@ -1353,6 +1365,8 @@ static int filter_pred_fn_call(struct filter_pred *pred, void *event)
return filter_pred_function(pred, event);
case FILTER_PRED_TEST_VISITED:
return test_pred_visited_fn(pred, event);
+ case FILTER_PRED_FN_MEMCMP:
+ return filter_pred_memcmp(pred, event);
default:
return 0;
}
@@ -1631,6 +1645,45 @@ static int parse_pred(const char *str, void *data,
/* go past the last quote */
i++;

+ } else if (str[i] == '0' && tolower(str[i + 1]) == 'x' &&
+ field->size > 8) {
+ u8 *pred_val;
+
+ /* For sizes > 8 bytes, we store a binary representation
+ * for comparison; only '==' and '!=' are supported.
+ * To keep things simple, the predicate value must specify
+ * a value that matches the field size exactly, with leading
+ * 0s if necessary.
+ */
+ if (pred->op != OP_EQ && pred->op != OP_NE) {
+ parse_error(pe, FILT_ERR_ILLEGAL_FIELD_OP, pos + i);
+ goto err_free;
+ }
+
+ /* skip required 0x */
+ s += 2;
+ i += 2;
+
+ while (isalnum(str[i]))
+ i++;
+
+ len = i - s;
+ if (len != (field->size * 2)) {
+ parse_error(pe, FILT_ERR_ILLEGAL_FIELD_OP, pos + s);
+ goto err_free;
+ }
+
+ pred_val = kzalloc(field->size, GFP_KERNEL);
+ if (hex2bin(pred_val, str + s, field->size)) {
+ parse_error(pe, FILT_ERR_ILLEGAL_INTVAL, pos + s);
+ kfree(pred_val);
+ goto err_free;
+ }
+ pred->val = (u64)pred_val;
+ pred->fn_num = FILTER_PRED_FN_MEMCMP;
+ if (pred->op == OP_NE)
+ pred->not = 1;
+
} else if (isdigit(str[i]) || str[i] == '-') {

/* Make sure the field is not a string */
--
2.31.1