llc_conn_handler() creates a child socket before the passive-open path finishes publishing it to accept(). Two listener-side paths can leave that child behind. First, non-SABME frames could still allocate an incoming child even though only SABME is a valid passive-open request. Those frames never publish the child to accept() and leak the child socket and its device reference. Second, even for a valid SABME request, the child is created before the connection state machine reaches LLC_CONN_PRIM. If the SABME setup path fails first, or if a listener-owned SABME skb is dropped before the current tree can process it, that unpublished child can remain linked in the SAP tables with its held device reference while it is still invisible to accept(). Only create an incoming child for SABME commands. For non-SABME listener traffic, send stateless DM responses for DISC and command frames with P=1, and drop all other frames without entering the listener connection state machine. For listener-owned SABME traffic, do not create the child before backlog enqueue, so backlog-side drops in the current tree cannot strand an unpublished child. If direct SABME processing then fails before publication, roll that child back out of the SAP and release its device reference. This closes both leak paths while preserving the existing passive-open flow for successful SABME connections. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: Codex:gpt-5.4 Signed-off-by: Zihan Xi --- net/llc/llc_conn.c | 90 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/net/llc/llc_conn.c b/net/llc/llc_conn.c index 260460d50..421f0d993 100644 --- a/net/llc/llc_conn.c +++ b/net/llc/llc_conn.c @@ -771,10 +771,55 @@ static struct sock *llc_create_incoming_sock(struct sock *sk, return newsk; } +static struct sock *llc_create_incoming_sock_from_skb(struct sock *sk, + struct sk_buff *skb) +{ + struct llc_addr saddr, daddr; + + llc_pdu_decode_sa(skb, saddr.mac); + llc_pdu_decode_ssap(skb, &saddr.lsap); + llc_pdu_decode_da(skb, daddr.mac); + llc_pdu_decode_dsap(skb, &daddr.lsap); + + return llc_create_incoming_sock(sk, skb->dev, &saddr, &daddr); +} + +static void llc_release_incoming_sock(struct sock *sk) +{ + struct llc_sock *llc = llc_sk(sk); + + llc_sap_remove_socket(llc->sap, sk); + dev_put(llc->dev); + sock_orphan(sk); + llc_sk_free(sk); +} + +static void llc_conn_send_dm_rsp(struct llc_sap *sap, struct sk_buff *skb, + struct llc_addr *saddr, u8 f_bit) +{ + struct sk_buff *nskb; + int rc; + + nskb = llc_alloc_frame(NULL, skb->dev, LLC_PDU_TYPE_U, 0); + if (!nskb) + return; + + llc_pdu_header_init(nskb, LLC_PDU_TYPE_U, sap->laddr.lsap, + saddr->lsap, LLC_PDU_RSP); + llc_pdu_init_as_dm_rsp(nskb, f_bit); + rc = llc_mac_hdr_init(nskb, skb->dev->dev_addr, saddr->mac); + if (unlikely(rc)) + kfree_skb(nskb); + else + dev_queue_xmit(nskb); +} + void llc_conn_handler(struct llc_sap *sap, struct sk_buff *skb) { struct llc_addr saddr, daddr; + struct sock *newsk = NULL; struct sock *sk; + int rc; llc_pdu_decode_sa(skb, saddr.mac); llc_pdu_decode_ssap(skb, &saddr.lsap); @@ -795,11 +840,24 @@ void llc_conn_handler(struct llc_sap *sap, struct sk_buff *skb) * in the newly created struct sock private area. -acme */ if (unlikely(sk->sk_state == TCP_LISTEN)) { - struct sock *newsk = llc_create_incoming_sock(sk, skb->dev, - &saddr, &daddr); - if (!newsk) + if (llc_conn_ev_rx_sabme_cmd_pbit_set_x(sk, skb)) { + if (!llc_conn_ev_rx_disc_cmd_pbit_set_x(sk, skb)) { + u8 f_bit; + + llc_pdu_decode_pf_bit(skb, &f_bit); + llc_conn_send_dm_rsp(sap, skb, &saddr, f_bit); + } else if (!llc_conn_ev_rx_xxx_cmd_pbit_set_1(sk, + skb)) { + llc_conn_send_dm_rsp(sap, skb, &saddr, 1); + } goto drop_unlock; - skb_set_owner_r(skb, newsk); + } else if (!sock_owned_by_user(sk)) { + newsk = llc_create_incoming_sock(sk, skb->dev, &saddr, + &daddr); + if (!newsk) + goto drop_unlock; + skb_set_owner_r(skb, newsk); + } } else { /* * Can't be skb_set_owner_r, this will be done at the @@ -813,9 +871,13 @@ void llc_conn_handler(struct llc_sap *sap, struct sk_buff *skb) skb->sk = sk; skb->destructor = sock_efree; } - if (!sock_owned_by_user(sk)) - llc_conn_rcv(sk, skb); - else { + if (!sock_owned_by_user(sk)) { + rc = llc_conn_rcv(sk, skb); + if (unlikely(rc) && newsk) { + llc_release_incoming_sock(newsk); + goto out; + } + } else { dprintk("%s: adding to backlog...\n", __func__); llc_set_backlog_type(skb, LLC_PACKET); if (sk_add_backlog(sk, skb, READ_ONCE(sk->sk_rcvbuf))) @@ -852,12 +914,22 @@ static int llc_backlog_rcv(struct sock *sk, struct sk_buff *skb) { int rc = 0; struct llc_sock *llc = llc_sk(sk); + struct sock *newsk = NULL; if (likely(llc_backlog_type(skb) == LLC_PACKET)) { - if (likely(llc->state > 1)) /* not closed */ + if (likely(llc->state > 1)) { /* not closed */ + if (unlikely(sk->sk_state == TCP_LISTEN)) { + newsk = llc_create_incoming_sock_from_skb(sk, skb); + if (!newsk) + goto out_kfree_skb; + skb_set_owner_r(skb, newsk); + } rc = llc_conn_rcv(sk, skb); - else + if (unlikely(rc) && newsk) + llc_release_incoming_sock(newsk); + } else { goto out_kfree_skb; + } } else if (llc_backlog_type(skb) == LLC_EVENT) { /* timer expiration event */ if (likely(llc->state > 1)) /* not closed */ -- 2.43.0