diff --git a/include/net/phonet/pep.h b/include/net/phonet/pep.h index 645dddf5ce77..ca90016b1c73 100644 --- a/include/net/phonet/pep.h +++ b/include/net/phonet/pep.h @@ -11,6 +11,7 @@ #define NET_PHONET_PEP_H #include +#include #include struct pep_sock { @@ -34,6 +35,7 @@ struct pep_sock { u8 tx_fc; /* TX flow control */ u8 init_enable; /* auto-enable at creation */ u8 aligned; + struct work_struct backlog_work; }; static inline struct pep_sock *pep_sk(struct sock *sk) diff --git a/net/phonet/pep.c b/net/phonet/pep.c index 4dbf0914df7d..7917aa7232be 100644 --- a/net/phonet/pep.c +++ b/net/phonet/pep.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -646,6 +648,16 @@ static struct sock *pep_find_pipe(const struct hlist_head *hlist, return NULL; } +static void pep_sock_backlog_work(struct work_struct *work) +{ + struct pep_sock *pn = container_of(work, struct pep_sock, backlog_work); + struct sock *sk = &pn->pn_sk.sk; + + lock_sock(sk); + release_sock(sk); + sock_put(sk); +} + /* * Deliver an skb to a listening sock. * Socket lock must be held. @@ -671,8 +683,34 @@ static int pep_do_rcv(struct sock *sk, struct sk_buff *skb) /* Look for an existing pipe handle */ sknode = pep_find_pipe(&pn->hlist, &dst, pipe_handle); - if (sknode) - return sk_receive_skb(sknode, skb, 1); + if (sknode) { + if (sock_owned_by_user_nocheck(sk)) + return sk_receive_skb(sknode, skb, 1); + + /* slock path: queue to child's backlog to avoid deadlock */ + if (sk_filter_trim_cap(sknode, skb, 1)) { + sock_put(sknode); + kfree_skb(skb); + return NET_RX_SUCCESS; + } + skb->dev = NULL; + bh_lock_sock_nested(sknode); + if (sk_add_backlog(sknode, skb, READ_ONCE(sknode->sk_rcvbuf))) { + bh_unlock_sock(sknode); + sk_drops_inc(sknode); + sock_put(sknode); + kfree_skb(skb); + return NET_RX_DROP; + } + bh_unlock_sock(sknode); + if (!sock_owned_by_user_nocheck(sknode)) { + sock_hold(sknode); + if (!schedule_work(&pep_sk(sknode)->backlog_work)) + sock_put(sknode); + } + sock_put(sknode); + return NET_RX_SUCCESS; + } switch (hdr->message_id) { case PNS_PEP_CONNECT_REQ: @@ -754,6 +792,9 @@ static void pep_sock_close(struct sock *sk, long timeout) pn->ifindex = 0; release_sock(sk); + if (sk->sk_backlog_rcv == pipe_do_rcv) + cancel_work_sync(&pn->backlog_work); + if (ifindex) gprs_detach(sk); sock_put(sk); @@ -866,6 +907,7 @@ static struct sock *pep_sock_accept(struct sock *sk, newpn->rx_fc = newpn->tx_fc = PN_LEGACY_FLOW_CONTROL; newpn->init_enable = enabled; newpn->aligned = aligned; + INIT_WORK(&newpn->backlog_work, pep_sock_backlog_work); err = pep_accept_conn(newsk, skb); if (err) { @@ -998,6 +1040,7 @@ static int pep_init(struct sock *sk) pn->rx_fc = pn->tx_fc = PN_LEGACY_FLOW_CONTROL; pn->init_enable = 1; pn->aligned = 0; + INIT_WORK(&pn->backlog_work, pep_sock_backlog_work); return 0; }