tracing: Bug fixes for 4.15 and earlier
Subsystems (3)
Steven Rostedt
maintainerLinus,
While doing tests on tracing over the network, I found that the packets were getting corrupted. In the process I found three bugs. One was the culprit, but the other two scared me. After deeper investigation, they were not as major as I thought they were, due to a signed compared to an unsigned that prevented a negative number from doing actual harm.
The two bigger bugs:
- Mask the ring buffer data page length. There are data flags at the high bits of the length field. These were not cleared via the length function, and the length could return a negative number. (Although the number returned was unsigned, but was assigned to a signed number) Luckily, this value was compared to PAGE_SIZE which is unsigned and kept it from entering the path that could have caused damage.
- Check the page usage before reusing the ring buffer reader page. TCP increments the page ref when passing the page off to the network. The page is passed back to the ring buffer for use on free. But the page could still be in use by the TCP stack.
Minor bugs:
- Related to the first bug. No need to clear out the unused ring buffer data before sending to user space. It is now done by the ring buffer code itself.
- Reset pointers after free on error path. There were some cases in the error path that pointers were freed but not set to NULL, and could have them freed again, having a pointer freed twice.
Please pull the latest trace-v4.15-rc4 tree, which can be found at:
git://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-trace.git
trace-v4.15-rc4
Tag SHA1: bd8e15502dea4c62617f963e512652dbf566a811 Head SHA1: 4397f04575c44e1440ec2e49b6302785c95fd2f8
Jing Xia (1):
tracing: Fix crash when it fails to alloc ring buffer
Steven Rostedt (VMware) (4):
ring-buffer: Mask out the info bits when returning buffer page length
tracing: Remove extra zeroing out of the ring buffer page
ring-buffer: Do no reuse reader page if still in use
tracing: Fix possible double free on failure of allocating trace buffer
----
kernel/trace/ring_buffer.c | 12 +++++++++++- kernel/trace/trace.c | 13 ++++--------- 2 files changed, 15 insertions(+), 10 deletions(-)
Steven Rostedt
maintainerPATCH 1/5 ring-buffer: Mask out the info bits when returning buffer page length
From: "Steven Rostedt (VMware)" <[email protected]>
Two info bits were added to the "commit" part of the ring buffer data page when returned to be consumed. This was to inform the user space readers that events have been missed, and that the count may be stored at the end of the page.
What wasn't handled, was the splice code that actually called a function to return the length of the data in order to zero out the rest of the page before sending it up to user space. These data bits were returned with the length making the value negative, and that negative value was not checked. It was compared to PAGE_SIZE, and only used if the size was less than PAGE_SIZE. Luckily PAGE_SIZE is unsigned long which made the compare an unsigned compare, meaning the negative size value did not end up causing a large portion of memory to be randomly zeroed out.
--- kernel/trace/ring_buffer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
kernel/trace/ring_buffer.c +5 -1
@@ -280,6 +280,8 @@ EXPORT_SYMBOL_GPL(ring_buffer_event_data);/* Missed count stored at end */ #define RB_MISSED_STORED (1 << 30) +#define RB_MISSED_FLAGS (RB_MISSED_EVENTS|RB_MISSED_STORED) +struct buffer_data_page {u64 time_stamp;/* page time stamp */ local_t commit;/* write committed index */
@@ -331,7 +333,9 @@ static void rb_init_page(struct buffer_data_page *bpage)*/ size_t ring_buffer_page_len (void *page) { -return local_read (&((struct buffer_data_page *)page)->commit) +struct buffer_data_page *bpage = page; + +return (local_read (&bpage->commit) & ~RB_MISSED_FLAGS ) +BUF_PAGE_HDR_SIZE ; }
Signature · 1 line
2.13.2
Steven Rostedt
maintainerPATCH 2/5 tracing: Remove extra zeroing out of the ring buffer page
In reply to Steven RostedtFrom: "Steven Rostedt (VMware)" <[email protected]>
The ring_buffer_read_page() takes care of zeroing out any extra data in the page that it returns. There's no need to zero it out again from the consumer. It was removed from one consumer of this function, but read_buffers_splice_read() did not remove it, and worse, it contained a nasty bug because of it.
--- kernel/trace/trace.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-)
kernel/trace/trace.c +1 -9
@@ -6769,7 +6769,7 @@ tracing_buffers_splice_read(struct file *file, loff_t *ppos, .spd_release = buffer_spd_release, };struct buffer_ref *ref; -int entries, size, i; +int entries, i;ssize_t ret =0 ;#ifdef CONFIG_TRACER_MAX_TRACE
@@ -6823,14 +6823,6 @@ tracing_buffers_splice_read(struct file *file, loff_t *ppos,break ; } -/* -* zero out any left over data, this is going to -* user land. -*/ - size =ring_buffer_page_len (ref->page); -if (size <PAGE_SIZE ) -memset (ref->page + size,0 ,PAGE_SIZE - size); - page =virt_to_page (ref->page); spd.pages[i] = page;
Signature · 1 line
2.13.2
Steven Rostedt
maintainerPATCH 3/5 ring-buffer: Do no reuse reader page if still in use
In reply to Steven RostedtFrom: "Steven Rostedt (VMware)" <[email protected]>
To free the reader page that is allocated with ring_buffer_alloc_read_page(), ring_buffer_free_read_page() must be called. For faster performance, this page can be reused by the ring buffer to avoid having to free and allocate new pages.
The issue arises when the page is used with a splice pipe into the networking code. The networking code may up the page counter for the page, and keep it active while sending it is queued to go to the network. The incrementing of the page ref does not prevent it from being reused in the ring buffer, and this can cause the page that is being sent out to the network to be modified before it is sent by reading new data.
Add a check to the page ref counter, and only reuse the page if it is not being used anywhere else.
--- kernel/trace/ring_buffer.c | 6 ++++++ 1 file changed, 6 insertions(+)
kernel/trace/ring_buffer.c +6 -0
@@ -4404,8 +4404,13 @@ void ring_buffer_free_read_page(struct ring_buffer *buffer, int cpu, void *data) {struct ring_buffer_per_cpu *cpu_buffer = buffer->buffers[cpu];struct buffer_data_page *bpage = data; +struct page *page =virt_to_page (bpage);unsigned long flags; +/* If the page is still in use someplace else, we can't reuse it */ +if (page_ref_count (page) >1 ) + goto out; +local_irq_save (flags);arch_spin_lock (&cpu_buffer->lock);
@@ -4417,6 +4422,7 @@ void ring_buffer_free_read_page(struct ring_buffer *buffer, int cpu, void *data)arch_spin_unlock (&cpu_buffer->lock);local_irq_restore (flags); + out:free_page ((unsigned long )bpage); }EXPORT_SYMBOL_GPL (ring_buffer_free_read_page);
Signature · 1 line
2.13.2
Steven Rostedt
maintainerPATCH 4/5 tracing: Fix crash when it fails to alloc ring buffer
In reply to Steven RostedtFrom: Jing Xia <[email protected]>
Double free of the ring buffer happens when it fails to alloc new ring buffer instance for max_buffer if TRACER_MAX_TRACE is configured. The root cause is that the pointer is not set to NULL after the buffer is freed in allocate_trace_buffers(), and the freeing of the ring buffer is invoked again later if the pointer is not equal to Null, as:
instance_mkdir()
|-allocate_trace_buffers()
|-allocate_trace_buffer(tr, &tr->trace_buffer...)
|-allocate_trace_buffer(tr, &tr->max_buffer...)
// allocate fail(-ENOMEM),first free
// and the buffer pointer is not set to null
|-ring_buffer_free(tr->trace_buffer.buffer)
// out_free_tr
|-free_trace_buffers()
|-free_trace_buffer(&tr->trace_buffer);
//if trace_buffer is not null, free again
|-ring_buffer_free(buf->buffer)
|-rb_free_cpu_buffer(buffer->buffers[cpu])
// ring_buffer_per_cpu is null, and
// crash in ring_buffer_per_cpu->pages--- kernel/trace/trace.c | 2 ++ 1 file changed, 2 insertions(+)
kernel/trace/trace.c +2 -0
@@ -7603,7 +7603,9 @@ static int allocate_trace_buffers(struct trace_array *tr, int size) allocate_snapshot ? size : 1);if (WARN_ON (ret)) {ring_buffer_free (tr->trace_buffer.buffer); + tr->trace_buffer.buffer =NULL ;free_percpu (tr->trace_buffer.data); + tr->trace_buffer.data =NULL ;return -ENOMEM ; } tr->allocated_snapshot = allocate_snapshot;
Signature · 1 line
2.13.2
Steven Rostedt
maintainerPATCH 5/5 tracing: Fix possible double free on failure of allocating trace buffer
In reply to Steven RostedtFrom: "Steven Rostedt (VMware)" <[email protected]>
Jing Xia and Chunyan Zhang reported that on failing to allocate part of the tracing buffer, memory is freed, but the pointers that point to them are not initialized back to NULL, and later paths may try to free the freed memory again. Jing and Chunyan fixed one of the locations that does this, but missed a spot.
--- kernel/trace/trace.c | 1 + 1 file changed, 1 insertion(+)
kernel/trace/trace.c +1 -0
@@ -7580,6 +7580,7 @@ allocate_trace_buffer(struct trace_array *tr, struct trace_buffer *buf, int size buf->data = alloc_percpu(struct trace_array_cpu);if (!buf->data) {ring_buffer_free (buf->buffer); + buf->buffer =NULL ;return -ENOMEM ; }
Signature · 1 line
2.13.2