Both have the same shape: a histogram field ends up flagged as a
stacktrace while the ftrace_event_field behind it does not describe one.
HIST_FIELD_FN_STACK then reads a __data_loc word out of the record and
follows it, and event_hist_trigger() uses the word it lands on as the
length of a memcpy into a 31 entry array. Neither end of that copy is
bounded. One write to tracefs is enough to take the machine down on a
kernel built with no debug options.
They are independent and have different Fixes tags, so they are two
patches rather than one.
Tested on x86_64:
- both reproducers panic before and are clean after, with and without
CONFIG_KASAN
- ftracetest test.d/trigger gives identical results for all 45 items
before and after (32 pass, 3 fail, 2 unresolved, 8 unsupported; the
failures are pre-existing and unrelated)
- the stacktrace modifier recipe from cc5fc8bfc961 still records
stacktraces
Donggeun Yoo (2):
tracing: Fix memory corruption from the stacktrace modifier
tracing: Fix memory corruption from a "STACKTRACE" histogram key
kernel/trace/trace_events_hist.c | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
base-commit: df2908090cda368b01ff43709f51890076c56157
--
2.53.0
parse_field() sets HIST_FIELD_FL_STACKTRACE from the ".stacktrace"
modifier before it looks the field name up, and nothing afterwards
checks that the name resolved to a field which holds a stacktrace.
create_hist_field() picks HIST_FIELD_FN_STACK on the strength of the
field pointer alone, which reads a __data_loc word from the record and
follows its low 16 bits as an offset into the same record.
event_hist_trigger() takes the first word there as an entry count and
copies that many longs into a 31 entry array:
n_entries = *stack;
memcpy(entries, ++stack, n_entries * sizeof(unsigned long));
Neither end of that copy is bounded, and the count is whatever the event
holds at the offset, so any field will do:
# cd /sys/kernel/tracing/events/sched/sched_process_fork
# echo 'hist:keys=parent_pid.stacktrace' > trigger
# (true)
BUG: kernel NULL pointer dereference, address: 0000000000000008
RIP: 0010:rb_insert_color+0x18/0x130
timerqueue_linked_add+0x7e/0xd0
enqueue_hrtimer+0x39/0xb0
__hrtimer_run_queues+0x10f/0x1f0
</IRQ>
RIP: 0010:memcpy+0xc/0x30
event_hist_trigger+0x165/0x690
The timer interrupt landed on the rbtree the copy had already run over.
No debug options are needed for this; KASAN reports the same write as an
out-of-bounds read of 13835058055416381440 bytes.
Documentation/trace/histogram.rst already states the rule, "must be a
long[] type", so enforce it once the name has been resolved. Names which
resolve to no field at all, "hitcount.stacktrace" and the common_*
pseudo-fields, are refused for the same reason: they hold no stacktrace
to read.
Fixes: cc5fc8bfc961 ("tracing/histogram: Add stacktrace type")
Cc: stable@vger.kernel.org
Signed-off-by: Donggeun Yoo <redacted>
---
This rejects triggers that used to be accepted. None of them could
produce a usable histogram, the key was either whatever the memcpy()
left behind or an unrelated value, so I took an error over silently
reading the current stack instead.
kernel/trace/trace_events_hist.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
"cpu", "CPU", "stacktrace" and "STACKTRACE" are generic fields, defined
with an offset and a size of zero so that the filter code can match them
by name. parse_field() maps them onto their common_* equivalents for
backward compatibility, but unlike the common_* names it hands the
placeholder back to the caller instead of NULL.
create_hist_field() takes a non-NULL field as a promise that the record
carries a stacktrace and picks HIST_FIELD_FN_STACK, so the __data_loc
word is read from offset 0, that is from common_type, and its low 16
bits are followed as an offset into the record. What is found there
becomes the length of an unbounded memcpy. Pick an event whose id is
small enough that the offset stays inside its own record and the length
is a kernel text address:
# cd /sys/kernel/tracing
# echo 'hist:keys=STACKTRACE' > events/ftrace/print/trigger
# echo hello > trace_marker
Oops: general protection fault, probably for non-canonical address
RIP: 0010:rb_next+0x23/0x60
</IRQ>
RIP: 0010:memcpy+0xc/0x30
event_hist_trigger+0x2e7/0x12c0
Kernel panic - not syncing: Fatal exception in interrupt
Leave the field NULL, which is what the comment above the branch says
the code does and what common_stacktrace already does. FILTER_CPU and
FILTER_COMM are left alone, their create_hist_field() branches never
look at the field.
Fixes: 4b512860bdbd ("tracing: Rename stacktrace field to common_stacktrace")
Cc: stable@vger.kernel.org
Signed-off-by: Donggeun Yoo <redacted>
---
'hist:keys=STACKTRACE' now reads back as 'hist:keys=common_stacktrace'
rather than 'hist:keys=STACKTRACE.stacktrace', since hist_field->field
is what the print side keys off.
kernel/trace/trace_events_hist.c | 1 +
1 file changed, 1 insertion(+)