Skip to content

Commit 2242fd5

Browse files
Martin KaFai LauAlexei Starovoitov
authored andcommitted
bpf: Avoid iter->offset making backward progress in bpf_iter_udp
There is a bug in the bpf_iter_udp_batch() function that stops the userspace from making forward progress. The case that triggers the bug is the userspace passed in a very small read buffer. When the bpf prog does bpf_seq_printf, the userspace read buffer is not enough to capture the whole bucket. When the read buffer is not large enough, the kernel will remember the offset of the bucket in iter->offset such that the next userspace read() can continue from where it left off. The kernel will skip the number (== "iter->offset") of sockets in the next read(). However, the code directly decrements the "--iter->offset". This is incorrect because the next read() may not consume the whole bucket either and then the next-next read() will start from offset 0. The net effect is the userspace will keep reading from the beginning of a bucket and the process will never finish. "iter->offset" must always go forward until the whole bucket is consumed. This patch fixes it by using a local variable "resume_offset" and "resume_bucket". "iter->offset" is always reset to 0 before it may be used. "iter->offset" will be advanced to the "resume_offset" when it continues from the "resume_bucket" (i.e. "state->bucket == resume_bucket"). This brings it closer to the bpf_iter_tcp's offset handling which does not suffer the same bug. Cc: Aditi Ghag <[email protected]> Fixes: c96dac8 ("bpf: udp: Implement batching for sockets iterator") Acked-by: Yonghong Song <[email protected]> Signed-off-by: Martin KaFai Lau <[email protected]> Reviewed-by: Aditi Ghag <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent 19ca082 commit 2242fd5

File tree

1 file changed

+10
-11
lines changed

1 file changed

+10
-11
lines changed

net/ipv4/udp.c

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3137,16 +3137,18 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
31373137
struct bpf_udp_iter_state *iter = seq->private;
31383138
struct udp_iter_state *state = &iter->state;
31393139
struct net *net = seq_file_net(seq);
3140+
int resume_bucket, resume_offset;
31403141
struct udp_table *udptable;
31413142
unsigned int batch_sks = 0;
31423143
bool resized = false;
31433144
struct sock *sk;
31443145

3146+
resume_bucket = state->bucket;
3147+
resume_offset = iter->offset;
3148+
31453149
/* The current batch is done, so advance the bucket. */
3146-
if (iter->st_bucket_done) {
3150+
if (iter->st_bucket_done)
31473151
state->bucket++;
3148-
iter->offset = 0;
3149-
}
31503152

31513153
udptable = udp_get_table_seq(seq, net);
31523154

@@ -3166,19 +3168,19 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
31663168
for (; state->bucket <= udptable->mask; state->bucket++) {
31673169
struct udp_hslot *hslot2 = &udptable->hash2[state->bucket];
31683170

3169-
if (hlist_empty(&hslot2->head)) {
3170-
iter->offset = 0;
3171+
if (hlist_empty(&hslot2->head))
31713172
continue;
3172-
}
31733173

3174+
iter->offset = 0;
31743175
spin_lock_bh(&hslot2->lock);
31753176
udp_portaddr_for_each_entry(sk, &hslot2->head) {
31763177
if (seq_sk_match(seq, sk)) {
31773178
/* Resume from the last iterated socket at the
31783179
* offset in the bucket before iterator was stopped.
31793180
*/
3180-
if (iter->offset) {
3181-
--iter->offset;
3181+
if (state->bucket == resume_bucket &&
3182+
iter->offset < resume_offset) {
3183+
++iter->offset;
31823184
continue;
31833185
}
31843186
if (iter->end_sk < iter->max_sk) {
@@ -3192,9 +3194,6 @@ static struct sock *bpf_iter_udp_batch(struct seq_file *seq)
31923194

31933195
if (iter->end_sk)
31943196
break;
3195-
3196-
/* Reset the current bucket's offset before moving to the next bucket. */
3197-
iter->offset = 0;
31983197
}
31993198

32003199
/* All done: no batch made. */

0 commit comments

Comments
 (0)