libbpf: Fix signed overflow in ringbuf_process_ring
PATCH 1/1 libbpf: Fix signed overflow in ringbuf_process_ring
One of our benchmarks running in (Google-internal) CI pushes data through the ringbuf faster than userspace is able to consume it. In this case it seems we're actually able to get >INT_MAX entries in a single ringbuf_buffer__consume call. ASAN detected that cnt overflows in this case.
Fix by just setting a limit on the number of entries that can be consumed.
--- tools/lib/bpf/ringbuf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
tools/lib/bpf/ringbuf.c +2 -2
@@ -213,8 +213,8 @@ static int ringbuf_process_ring(struct ring* r) do { got_new_data = false; prod_pos = smp_load_acquire(r->producer_pos); - while (cons_pos < prod_pos) { + /* Don't read more than INT_MAX, or the return vale won't make sense. */ + while (cons_pos < prod_pos && cnt < INT_MAX) { len_ptr = r->data + (cons_pos & r->mask); len = smp_load_acquire(len_ptr); --
2.31.1.498.g6c1eba8ee3d-goog
Quoted 21 lines · Brendan Jackman
One of our benchmarks running in (Google-internal) CI pushes data through the ringbuf faster than userspace is able to consume it. In this case it seems we're actually able to get >INT_MAX entries in a single
ringbuf_buffer__consumecall. ASAN detected that cnt overflows in this case.Fix by just setting a limit on the number of entries that can be consumed.
Fixes bf99c936f947 (libbpf: Add BPF ring buffer support)Signed-off-by Brendan Jackman <[email protected]>--- tools/lib/bpf/ringbuf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)tools/lib/bpf/ringbuf.c +0
-0
tools/lib/bpf/ringbuf.c static int ringbuf_process_ring(struct ring* r) · line 217
Quoted 6 lines
tools/lib/bpf/ringbuf.c +2
-1@@ -213,8 +213,8 @@ static int ringbuf_process_ring(struct ring* r) do { got_new_data = false; prod_pos = smp_load_acquire(r->producer_pos); - while (cons_pos < prod_pos) { + /* Don't read more than INT_MAX, or the return vale won't make sense. */ + while (cons_pos < prod_pos && cnt < INT_MAX) {
ring_buffer__pool() is assumed to not return until all the enqueued messages are consumed. That's the requirement for the "adaptive" notification scheme to work properly. So this will break that and cause the next ring_buffer__pool() to never wake up.
We could use __u64 internally and then cap it to INT_MAX on return maybe? But honestly, this sounds like an artificial corner case, if you are producing data faster than you can consume it and it goes beyond INT_MAX, something is seriously broken in your application and you have more important things to handle :)
Quoted 6 lines
tools/lib/bpf/ringbuf.c +0
-1len_ptr = r->data + (cons_pos & r->mask); len =smp_load_acquire (len_ptr); --2.31.1.498.g6c1eba8ee3d-goog
Quoted 35 lines · Andrii Nakryiko
Quoted 21 lines · Brendan Jackman
One of our benchmarks running in (Google-internal) CI pushes data through the ringbuf faster than userspace is able to consume it. In this case it seems we're actually able to get >INT_MAX entries in a single
ringbuf_buffer__consumecall. ASAN detected that cnt overflows in this case.Fix by just setting a limit on the number of entries that can be consumed.
Fixes bf99c936f947 (libbpf: Add BPF ring buffer support)Signed-off-by Brendan Jackman <[email protected]>--- tools/lib/bpf/ringbuf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)tools/lib/bpf/ringbuf.c +0
-0tools/lib/bpf/ringbuf.c static int ringbuf_process_ring(struct ring* r) · line 217
Quoted 6 lines
tools/lib/bpf/ringbuf.c +2
-1@@ -213,8 +213,8 @@ static int ringbuf_process_ring(struct ring* r) do { got_new_data = false; prod_pos = smp_load_acquire(r->producer_pos); - while (cons_pos < prod_pos) { + /* Don't read more than INT_MAX, or the return vale won't make sense. */ + while (cons_pos < prod_pos && cnt < INT_MAX) {
ring_buffer__pool()is assumed to not return until all the enqueued messages are consumed. That's the requirement for the "adaptive" notification scheme to work properly. So this will break that and cause the nextring_buffer__pool()to never wake up.
Quoted 4 lines
We could use
__u64internally and then cap it toINT_MAXon return maybe? But honestly, this sounds like an artificial corner case, if you are producing data faster than you can consume it and it goes beyondINT_MAX, something is seriously broken in your application and
Disclaimer: I don't know what Brendan's benchmark is actually doing
That said, I have seen similar boundaries being reached when doing process monitoring and then a kernel gets compiled (esp. with ccache) and generates a large amount of process events in a very short span of time. Another example is when someone runs a short process in a tight while loop.
I agree it's a matter of tuning, but since these corner cases can be easily triggered
even on real (non CI) systems no matter how much one tunes, I wouldn't really call it artificial :)
- KP
Quoted 8 lines
you have more important things to handle :)
Quoted 6 lines
tools/lib/bpf/ringbuf.c +0
-1len_ptr = r->data + (cons_pos & r->mask); len =smp_load_acquire (len_ptr); --2.31.1.498.g6c1eba8ee3d-goog
Quoted 50 lines · KP Singh
Quoted 35 lines · Andrii Nakryiko
Quoted 21 lines · Brendan Jackman
One of our benchmarks running in (Google-internal) CI pushes data through the ringbuf faster than userspace is able to consume it. In this case it seems we're actually able to get >INT_MAX entries in a single
ringbuf_buffer__consumecall. ASAN detected that cnt overflows in this case.Fix by just setting a limit on the number of entries that can be consumed.
Fixes bf99c936f947 (libbpf: Add BPF ring buffer support)Signed-off-by Brendan Jackman <[email protected]>--- tools/lib/bpf/ringbuf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)tools/lib/bpf/ringbuf.c +0
-0tools/lib/bpf/ringbuf.c static int ringbuf_process_ring(struct ring* r) · line 217
Quoted 6 lines
tools/lib/bpf/ringbuf.c +2
-1@@ -213,8 +213,8 @@ static int ringbuf_process_ring(struct ring* r) do { got_new_data = false; prod_pos = smp_load_acquire(r->producer_pos); - while (cons_pos < prod_pos) { + /* Don't read more than INT_MAX, or the return vale won't make sense. */ + while (cons_pos < prod_pos && cnt < INT_MAX) {
ring_buffer__pool()is assumed to not return until all the enqueued messages are consumed. That's the requirement for the "adaptive" notification scheme to work properly. So this will break that and cause the nextring_buffer__pool()to never wake up.Quoted 4 lines
We could use
__u64internally and then cap it toINT_MAXon return maybe? But honestly, this sounds like an artificial corner case, if you are producing data faster than you can consume it and it goes beyondINT_MAX, something is seriously broken in your application andDisclaimer: I don't know what Brendan's benchmark is actually doing
That said, I have seen similar boundaries being reached when doing process monitoring and then a kernel gets compiled (esp. with ccache) and generates a large amount of process events in a very short span of time. Another example is when someone runs a short process in a tight while loop.
Quoted 4 lines
I agree it's a matter of tuning, but since these corner cases can be easily triggered
even on real (non CI) systems no matter how much one tunes, I wouldn't really call it artificial :)
Well of course, given sufficiently active kernel sample producer and sufficiently slow consumer you can keep consuming forever.
I think we have two alternatives here:
1) consume all but cap return to INT_MAX
2) consume all but return long long as return result
Third alternative is to have another API with maximum number of samples to consume. But then user needs to know what they are doing (e.g., they do FORCE on BPF side, or they do their own epoll_wait, or they do ring_buffer__poll with timeout = 0, etc).
I'm just not sure anyone would want to understand all the implications. And it's easy to miss those implications. So maybe let's do long long (or __s64) return type instead?
Quoted 11 lines
- KP
Quoted 8 lines
you have more important things to handle :)
Quoted 6 lines
tools/lib/bpf/ringbuf.c +0
-1len_ptr = r->data + (cons_pos & r->mask); len =smp_load_acquire (len_ptr); --2.31.1.498.g6c1eba8ee3d-goog
Quoted 23 lines · Andrii Nakryiko
Quoted 21 lines · KP Singh
Quoted 18 lines · Andrii Nakryiko
Quoted 16 lines · Brendan Jackman
One of our benchmarks running in (Google-internal) CI pushes data through the ringbuf faster than userspace is able to consume it. In this case it seems we're actually able to get >INT_MAX entries in a single
ringbuf_buffer__consumecall. ASAN detected that cnt overflows in this case.Fix by just setting a limit on the number of entries that can be consumed.
Fixes bf99c936f947 (libbpf: Add BPF ring buffer support)Signed-off-by Brendan Jackman <[email protected]>--- tools/lib/bpf/ringbuf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
Quoted 16 lines
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
tools/lib/bpf/ringbuf.c +0
-0@@ -213,8 +213,8 @@ static int ringbuf_process_ring(struct ring* r) do { got_new_data = false; prod_pos = smp_load_acquire(r->producer_pos);Quoted 8 lines
Quoted 8 lines
tools/lib/bpf/ringbuf.c static int ringbuf_process_ring(struct ring* r) · line 217
Quoted 3 lines
tools/lib/bpf/ringbuf.c +2
-1-while (cons_pos < prod_pos) { +/* Don't read more than INT_MAX, or the return vale won't make sense. */ +while (cons_pos < prod_pos && cnt <INT_MAX ) {
ring_buffer__pool()is assumed to not return until all the enqueued messages are consumed. That's the requirement for the "adaptive" notification scheme to work properly. So this will break that and cause the nextring_buffer__pool()to never wake up.
Ah yes, good point, thanks.
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
We could use
__u64internally and then cap it toINT_MAXon return maybe? But honestly, this sounds like an artificial corner case, if you are producing data faster than you can consume it and it goes beyondINT_MAX, something is seriously broken in your application and
Yes it's certainly artificial but IMO it's still highly desirable for libbpf to hold up its side of the bargain even when the application is behaving very strangely like this.
[...]
Quoted 4 lines
I think we have two alternatives here:
1) consume all but cap return toINT_MAX
2) consume all but return long long as return result
Quoted 8 lines
Third alternative is to have another API with maximum number of samples to consume. But then user needs to know what they are doing (e.g., they do FORCE on BPF side, or they do their own
epoll_wait, or they doring_buffer__pollwith timeout = 0, etc).I'm just not sure anyone would want to understand all the implications. And it's easy to miss those implications. So maybe let's do long long (or
__s64) return type instead?
Wouldn't changing the API to 64 bit return type break existing users on some ABIs?
I think capping the return value to INT_MAX and adding a note to the function definition comment would also be fine, it doesn't feel like a very complex thing for the user to understand: "Returns number of records consumed (or INT_MAX, whichever is less)".
Quoted 44 lines · Brendan Jackman
Quoted 23 lines · Andrii Nakryiko
Quoted 21 lines · KP Singh
Quoted 18 lines · Andrii Nakryiko
Quoted 16 lines · Brendan Jackman
One of our benchmarks running in (Google-internal) CI pushes data through the ringbuf faster than userspace is able to consume it. In this case it seems we're actually able to get >INT_MAX entries in a single
ringbuf_buffer__consumecall. ASAN detected that cnt overflows in this case.Fix by just setting a limit on the number of entries that can be consumed.
Fixes bf99c936f947 (libbpf: Add BPF ring buffer support)Signed-off-by Brendan Jackman <[email protected]>--- tools/lib/bpf/ringbuf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)Quoted 16 lines
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
tools/lib/bpf/ringbuf.c +0
-0@@ -213,8 +213,8 @@ static int ringbuf_process_ring(struct ring* r) do { got_new_data = false; prod_pos = smp_load_acquire(r->producer_pos);Quoted 8 lines
Quoted 8 lines
tools/lib/bpf/ringbuf.c static int ringbuf_process_ring(struct ring* r) · line 217
Quoted 3 lines
tools/lib/bpf/ringbuf.c +2
-1-while (cons_pos < prod_pos) { +/* Don't read more than INT_MAX, or the return vale won't make sense. */ +while (cons_pos < prod_pos && cnt <INT_MAX ) {
ring_buffer__pool()is assumed to not return until all the enqueued messages are consumed. That's the requirement for the "adaptive" notification scheme to work properly. So this will break that and cause the nextring_buffer__pool()to never wake up.Ah yes, good point, thanks.
Quoted 8 lines
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
We could use
__u64internally and then cap it toINT_MAXon return maybe? But honestly, this sounds like an artificial corner case, if you are producing data faster than you can consume it and it goes beyondINT_MAX, something is seriously broken in your application andYes it's certainly artificial but IMO it's still highly desirable for libbpf to hold up its side of the bargain even when the application is behaving very strangely like this.
One can also argue that if application consumed more than 2 billion messages in one go, that's an error. ;-P But of course that is not great.
Quoted 12 lines
[...]
Quoted 9 lines
I think we have two alternatives here:
1) consume all but cap return toINT_MAX
2) consume all but return long long as return resultThird alternative is to have another API with maximum number of samples to consume. But then user needs to know what they are doing (e.g., they do FORCE on BPF side, or they do their own
epoll_wait, or they doring_buffer__pollwith timeout = 0, etc).
Quoted 7 lines
Quoted 3 lines
I'm just not sure anyone would want to understand all the implications. And it's easy to miss those implications. So maybe let's do long long (or
__s64) return type instead?Wouldn't changing the API to 64 bit return type break existing users on some ABIs?
Yes, it might, not perfect.
Quoted 4 lines
I think capping the return value to
INT_MAXand adding a note to the function definition comment would also be fine, it doesn't feel like a very complex thing for the user to understand: "Returns number of records consumed (orINT_MAX, whichever is less)".
Yep, let's cap. But to not penalize a hot loop with extra checks. Let's use int64_t internally for counting and only cap it before the return.
On Wed, 28 Apr 2021 at 20:13, Andrii Nakryiko <[email protected]> wrote:
...
Quoted 4 lines
Yep, let's cap. But to not penalize a hot loop with extra checks. Let's use
int64_tinternally for counting and only cap it before the return.
Cool, sounds good. Patch incoming but I'm on interrupts duty this week so might not be today/tomorrow.