Skip to contentSkip to conversation

tracing: Bug fixes for 4.15 and earlier

GIT PULL New Steven Rostedt 1 participant 6 messages Dec 27, 2017

Subsystems (3)
lore
Share & tools
lore

Steven Rostedt

author maintainer
#0 lore raw

Linus,

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(-)
author maintainer
#1 lore raw

PATCH 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.

Fixes 66a8cb95ed040 ("ring-buffer: Add place holder recording of dropped events")
---
 kernel/trace/ring_buffer.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
kernel/trace/ring_buffer.c +5 -1
index c87766c1c204..e06cde093f76 100644
@@ -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
author maintainer
#2 lore raw

PATCH 2/5 tracing: Remove extra zeroing out of the ring buffer page

In reply to Steven Rostedt

From: "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.

Fixes 2711ca237a084 ("ring-buffer: Move zeroing out excess in page to ring buffer code")
---
 kernel/trace/trace.c | 10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)
kernel/trace/trace.c +1 -9
index 59518b8126d0..73652d5318b2 100644
@@ -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
author maintainer
#3 lore raw

PATCH 3/5 ring-buffer: Do no reuse reader page if still in use

In reply to Steven Rostedt

From: "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.

Fixes 73a757e63114d ("ring-buffer: Return reader page back into existing ring buffer")
---
 kernel/trace/ring_buffer.c | 6 ++++++
 1 file changed, 6 insertions(+)
kernel/trace/ring_buffer.c +6 -0
index e06cde093f76..9ab18995ff1e 100644
@@ -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
author maintainer
#4 lore raw

PATCH 4/5 tracing: Fix crash when it fails to alloc ring buffer

In reply to Steven Rostedt

From: 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
Fixes 737223fbca3b1 ("tracing: Consolidate buffer allocation code")
---
 kernel/trace/trace.c | 2 ++
 1 file changed, 2 insertions(+)
kernel/trace/trace.c +2 -0
index 73652d5318b2..0e53d46544b8 100644
@@ -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
author maintainer
#5 lore raw

PATCH 5/5 tracing: Fix possible double free on failure of allocating trace buffer

In reply to Steven Rostedt

From: "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.

Fixes 737223fbca3b1 ("tracing: Consolidate buffer allocation code")
---
 kernel/trace/trace.c | 1 +
 1 file changed, 1 insertion(+)
kernel/trace/trace.c +1 -0
index 0e53d46544b8..2a8d8a294345 100644
@@ -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