diff options
author | Eric Biggers <ebiggers@google.com> | 2019-10-06 17:24:25 -0400 |
---|---|---|
committer | Jakub Kicinski <jakub.kicinski@netronome.com> | 2019-10-08 16:23:05 -0400 |
commit | b74555de21acd791f12c4a1aeaf653dd7ac21133 (patch) | |
tree | 861c5ed79b737209cbec21fb47e3b2a49e738576 | |
parent | c6ee11c39fcc1fb55130748990a8f199e76263b4 (diff) |
llc: fix sk_buff leak in llc_conn_service()
syzbot reported:
BUG: memory leak
unreferenced object 0xffff88811eb3de00 (size 224):
comm "syz-executor559", pid 7315, jiffies 4294943019 (age 10.300s)
hex dump (first 32 bytes):
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 a0 38 24 81 88 ff ff 00 c0 f2 15 81 88 ff ff ..8$............
backtrace:
[<000000008d1c66a1>] kmemleak_alloc_recursive include/linux/kmemleak.h:55 [inline]
[<000000008d1c66a1>] slab_post_alloc_hook mm/slab.h:439 [inline]
[<000000008d1c66a1>] slab_alloc_node mm/slab.c:3269 [inline]
[<000000008d1c66a1>] kmem_cache_alloc_node+0x153/0x2a0 mm/slab.c:3579
[<00000000447d9496>] __alloc_skb+0x6e/0x210 net/core/skbuff.c:198
[<000000000cdbf82f>] alloc_skb include/linux/skbuff.h:1058 [inline]
[<000000000cdbf82f>] llc_alloc_frame+0x66/0x110 net/llc/llc_sap.c:54
[<000000002418b52e>] llc_conn_ac_send_sabme_cmd_p_set_x+0x2f/0x140 net/llc/llc_c_ac.c:777
[<000000001372ae17>] llc_exec_conn_trans_actions net/llc/llc_conn.c:475 [inline]
[<000000001372ae17>] llc_conn_service net/llc/llc_conn.c:400 [inline]
[<000000001372ae17>] llc_conn_state_process+0x1ac/0x640 net/llc/llc_conn.c:75
[<00000000f27e53c1>] llc_establish_connection+0x110/0x170 net/llc/llc_if.c:109
[<00000000291b2ca0>] llc_ui_connect+0x10e/0x370 net/llc/af_llc.c:477
[<000000000f9c740b>] __sys_connect+0x11d/0x170 net/socket.c:1840
[...]
The bug is that most callers of llc_conn_send_pdu() assume it consumes a
reference to the skb, when actually due to commit b85ab56c3f81 ("llc:
properly handle dev_queue_xmit() return value") it doesn't.
Revert most of that commit, and instead make the few places that need
llc_conn_send_pdu() to *not* consume a reference call skb_get() before.
Fixes: b85ab56c3f81 ("llc: properly handle dev_queue_xmit() return value")
Reported-by: syzbot+6b825a6494a04cc0e3f7@syzkaller.appspotmail.com
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: Jakub Kicinski <jakub.kicinski@netronome.com>
-rw-r--r-- | include/net/llc_conn.h | 2 | ||||
-rw-r--r-- | net/llc/llc_c_ac.c | 8 | ||||
-rw-r--r-- | net/llc/llc_conn.c | 32 |
3 files changed, 16 insertions, 26 deletions
diff --git a/include/net/llc_conn.h b/include/net/llc_conn.h index df528a623548..ea985aa7a6c5 100644 --- a/include/net/llc_conn.h +++ b/include/net/llc_conn.h | |||
@@ -104,7 +104,7 @@ void llc_sk_reset(struct sock *sk); | |||
104 | 104 | ||
105 | /* Access to a connection */ | 105 | /* Access to a connection */ |
106 | int llc_conn_state_process(struct sock *sk, struct sk_buff *skb); | 106 | int llc_conn_state_process(struct sock *sk, struct sk_buff *skb); |
107 | int llc_conn_send_pdu(struct sock *sk, struct sk_buff *skb); | 107 | void llc_conn_send_pdu(struct sock *sk, struct sk_buff *skb); |
108 | void llc_conn_rtn_pdu(struct sock *sk, struct sk_buff *skb); | 108 | void llc_conn_rtn_pdu(struct sock *sk, struct sk_buff *skb); |
109 | void llc_conn_resend_i_pdu_as_cmd(struct sock *sk, u8 nr, u8 first_p_bit); | 109 | void llc_conn_resend_i_pdu_as_cmd(struct sock *sk, u8 nr, u8 first_p_bit); |
110 | void llc_conn_resend_i_pdu_as_rsp(struct sock *sk, u8 nr, u8 first_f_bit); | 110 | void llc_conn_resend_i_pdu_as_rsp(struct sock *sk, u8 nr, u8 first_f_bit); |
diff --git a/net/llc/llc_c_ac.c b/net/llc/llc_c_ac.c index 4d78375f9872..647c0554d04c 100644 --- a/net/llc/llc_c_ac.c +++ b/net/llc/llc_c_ac.c | |||
@@ -372,6 +372,7 @@ int llc_conn_ac_send_i_cmd_p_set_1(struct sock *sk, struct sk_buff *skb) | |||
372 | llc_pdu_init_as_i_cmd(skb, 1, llc->vS, llc->vR); | 372 | llc_pdu_init_as_i_cmd(skb, 1, llc->vS, llc->vR); |
373 | rc = llc_mac_hdr_init(skb, llc->dev->dev_addr, llc->daddr.mac); | 373 | rc = llc_mac_hdr_init(skb, llc->dev->dev_addr, llc->daddr.mac); |
374 | if (likely(!rc)) { | 374 | if (likely(!rc)) { |
375 | skb_get(skb); | ||
375 | llc_conn_send_pdu(sk, skb); | 376 | llc_conn_send_pdu(sk, skb); |
376 | llc_conn_ac_inc_vs_by_1(sk, skb); | 377 | llc_conn_ac_inc_vs_by_1(sk, skb); |
377 | } | 378 | } |
@@ -389,7 +390,8 @@ static int llc_conn_ac_send_i_cmd_p_set_0(struct sock *sk, struct sk_buff *skb) | |||
389 | llc_pdu_init_as_i_cmd(skb, 0, llc->vS, llc->vR); | 390 | llc_pdu_init_as_i_cmd(skb, 0, llc->vS, llc->vR); |
390 | rc = llc_mac_hdr_init(skb, llc->dev->dev_addr, llc->daddr.mac); | 391 | rc = llc_mac_hdr_init(skb, llc->dev->dev_addr, llc->daddr.mac); |
391 | if (likely(!rc)) { | 392 | if (likely(!rc)) { |
392 | rc = llc_conn_send_pdu(sk, skb); | 393 | skb_get(skb); |
394 | llc_conn_send_pdu(sk, skb); | ||
393 | llc_conn_ac_inc_vs_by_1(sk, skb); | 395 | llc_conn_ac_inc_vs_by_1(sk, skb); |
394 | } | 396 | } |
395 | return rc; | 397 | return rc; |
@@ -406,6 +408,7 @@ int llc_conn_ac_send_i_xxx_x_set_0(struct sock *sk, struct sk_buff *skb) | |||
406 | llc_pdu_init_as_i_cmd(skb, 0, llc->vS, llc->vR); | 408 | llc_pdu_init_as_i_cmd(skb, 0, llc->vS, llc->vR); |
407 | rc = llc_mac_hdr_init(skb, llc->dev->dev_addr, llc->daddr.mac); | 409 | rc = llc_mac_hdr_init(skb, llc->dev->dev_addr, llc->daddr.mac); |
408 | if (likely(!rc)) { | 410 | if (likely(!rc)) { |
411 | skb_get(skb); | ||
409 | llc_conn_send_pdu(sk, skb); | 412 | llc_conn_send_pdu(sk, skb); |
410 | llc_conn_ac_inc_vs_by_1(sk, skb); | 413 | llc_conn_ac_inc_vs_by_1(sk, skb); |
411 | } | 414 | } |
@@ -916,7 +919,8 @@ static int llc_conn_ac_send_i_rsp_f_set_ackpf(struct sock *sk, | |||
916 | llc_pdu_init_as_i_cmd(skb, llc->ack_pf, llc->vS, llc->vR); | 919 | llc_pdu_init_as_i_cmd(skb, llc->ack_pf, llc->vS, llc->vR); |
917 | rc = llc_mac_hdr_init(skb, llc->dev->dev_addr, llc->daddr.mac); | 920 | rc = llc_mac_hdr_init(skb, llc->dev->dev_addr, llc->daddr.mac); |
918 | if (likely(!rc)) { | 921 | if (likely(!rc)) { |
919 | rc = llc_conn_send_pdu(sk, skb); | 922 | skb_get(skb); |
923 | llc_conn_send_pdu(sk, skb); | ||
920 | llc_conn_ac_inc_vs_by_1(sk, skb); | 924 | llc_conn_ac_inc_vs_by_1(sk, skb); |
921 | } | 925 | } |
922 | return rc; | 926 | return rc; |
diff --git a/net/llc/llc_conn.c b/net/llc/llc_conn.c index 4ff89cb7c86f..ed2aca12460c 100644 --- a/net/llc/llc_conn.c +++ b/net/llc/llc_conn.c | |||
@@ -30,7 +30,7 @@ | |||
30 | #endif | 30 | #endif |
31 | 31 | ||
32 | static int llc_find_offset(int state, int ev_type); | 32 | static int llc_find_offset(int state, int ev_type); |
33 | static int llc_conn_send_pdus(struct sock *sk, struct sk_buff *skb); | 33 | static void llc_conn_send_pdus(struct sock *sk); |
34 | static int llc_conn_service(struct sock *sk, struct sk_buff *skb); | 34 | static int llc_conn_service(struct sock *sk, struct sk_buff *skb); |
35 | static int llc_exec_conn_trans_actions(struct sock *sk, | 35 | static int llc_exec_conn_trans_actions(struct sock *sk, |
36 | struct llc_conn_state_trans *trans, | 36 | struct llc_conn_state_trans *trans, |
@@ -193,11 +193,11 @@ out_skb_put: | |||
193 | return rc; | 193 | return rc; |
194 | } | 194 | } |
195 | 195 | ||
196 | int llc_conn_send_pdu(struct sock *sk, struct sk_buff *skb) | 196 | void llc_conn_send_pdu(struct sock *sk, struct sk_buff *skb) |
197 | { | 197 | { |
198 | /* queue PDU to send to MAC layer */ | 198 | /* queue PDU to send to MAC layer */ |
199 | skb_queue_tail(&sk->sk_write_queue, skb); | 199 | skb_queue_tail(&sk->sk_write_queue, skb); |
200 | return llc_conn_send_pdus(sk, skb); | 200 | llc_conn_send_pdus(sk); |
201 | } | 201 | } |
202 | 202 | ||
203 | /** | 203 | /** |
@@ -255,7 +255,7 @@ void llc_conn_resend_i_pdu_as_cmd(struct sock *sk, u8 nr, u8 first_p_bit) | |||
255 | if (howmany_resend > 0) | 255 | if (howmany_resend > 0) |
256 | llc->vS = (llc->vS + 1) % LLC_2_SEQ_NBR_MODULO; | 256 | llc->vS = (llc->vS + 1) % LLC_2_SEQ_NBR_MODULO; |
257 | /* any PDUs to re-send are queued up; start sending to MAC */ | 257 | /* any PDUs to re-send are queued up; start sending to MAC */ |
258 | llc_conn_send_pdus(sk, NULL); | 258 | llc_conn_send_pdus(sk); |
259 | out:; | 259 | out:; |
260 | } | 260 | } |
261 | 261 | ||
@@ -296,7 +296,7 @@ void llc_conn_resend_i_pdu_as_rsp(struct sock *sk, u8 nr, u8 first_f_bit) | |||
296 | if (howmany_resend > 0) | 296 | if (howmany_resend > 0) |
297 | llc->vS = (llc->vS + 1) % LLC_2_SEQ_NBR_MODULO; | 297 | llc->vS = (llc->vS + 1) % LLC_2_SEQ_NBR_MODULO; |
298 | /* any PDUs to re-send are queued up; start sending to MAC */ | 298 | /* any PDUs to re-send are queued up; start sending to MAC */ |
299 | llc_conn_send_pdus(sk, NULL); | 299 | llc_conn_send_pdus(sk); |
300 | out:; | 300 | out:; |
301 | } | 301 | } |
302 | 302 | ||
@@ -340,16 +340,12 @@ out: | |||
340 | /** | 340 | /** |
341 | * llc_conn_send_pdus - Sends queued PDUs | 341 | * llc_conn_send_pdus - Sends queued PDUs |
342 | * @sk: active connection | 342 | * @sk: active connection |
343 | * @hold_skb: the skb held by caller, or NULL if does not care | ||
344 | * | 343 | * |
345 | * Sends queued pdus to MAC layer for transmission. When @hold_skb is | 344 | * Sends queued pdus to MAC layer for transmission. |
346 | * NULL, always return 0. Otherwise, return 0 if @hold_skb is sent | ||
347 | * successfully, or 1 for failure. | ||
348 | */ | 345 | */ |
349 | static int llc_conn_send_pdus(struct sock *sk, struct sk_buff *hold_skb) | 346 | static void llc_conn_send_pdus(struct sock *sk) |
350 | { | 347 | { |
351 | struct sk_buff *skb; | 348 | struct sk_buff *skb; |
352 | int ret = 0; | ||
353 | 349 | ||
354 | while ((skb = skb_dequeue(&sk->sk_write_queue)) != NULL) { | 350 | while ((skb = skb_dequeue(&sk->sk_write_queue)) != NULL) { |
355 | struct llc_pdu_sn *pdu = llc_pdu_sn_hdr(skb); | 351 | struct llc_pdu_sn *pdu = llc_pdu_sn_hdr(skb); |
@@ -361,20 +357,10 @@ static int llc_conn_send_pdus(struct sock *sk, struct sk_buff *hold_skb) | |||
361 | skb_queue_tail(&llc_sk(sk)->pdu_unack_q, skb); | 357 | skb_queue_tail(&llc_sk(sk)->pdu_unack_q, skb); |
362 | if (!skb2) | 358 | if (!skb2) |
363 | break; | 359 | break; |
364 | dev_queue_xmit(skb2); | 360 | skb = skb2; |
365 | } else { | ||
366 | bool is_target = skb == hold_skb; | ||
367 | int rc; | ||
368 | |||
369 | if (is_target) | ||
370 | skb_get(skb); | ||
371 | rc = dev_queue_xmit(skb); | ||
372 | if (is_target) | ||
373 | ret = rc; | ||
374 | } | 361 | } |
362 | dev_queue_xmit(skb); | ||
375 | } | 363 | } |
376 | |||
377 | return ret; | ||
378 | } | 364 | } |
379 | 365 | ||
380 | /** | 366 | /** |