diff options
Diffstat (limited to 'net')
62 files changed, 780 insertions, 303 deletions
diff --git a/net/bluetooth/eir.c b/net/bluetooth/eir.c index 1de5f9df6eec..a55696820b22 100644 --- a/net/bluetooth/eir.c +++ b/net/bluetooth/eir.c @@ -369,6 +369,7 @@ u8 eir_create_scan_rsp(struct hci_dev *hdev, u8 instance, u8 *ptr) void *eir_get_service_data(u8 *eir, size_t eir_len, u16 uuid, size_t *len) { + const u8 *eir_end = eir + eir_len; size_t dlen; while ((eir = eir_get_data(eir, eir_len, EIR_SERVICE_DATA, &dlen))) { @@ -381,7 +382,7 @@ void *eir_get_service_data(u8 *eir, size_t eir_len, u16 uuid, size_t *len) } eir += dlen; - eir_len -= dlen; + eir_len = eir_end - eir; } return NULL; diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index b1f911fd4ad6..7e4d737a795f 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -1381,7 +1381,8 @@ static void hci_le_conn_failed(struct hci_conn *conn, u8 status) /* Enable advertising in case this was a failed connection * attempt as a peripheral. */ - hci_enable_advertising(hdev); + if (conn->role == HCI_ROLE_SLAVE) + hci_enable_advertising(hdev); } /* This function requires the caller holds hdev->lock */ diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 5ba9fe8261ec..f346505c3f88 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -4091,7 +4091,7 @@ static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb) if (READ_ONCE(hdev->req_status) == HCI_REQ_PEND && !hci_dev_test_and_set_flag(hdev, HCI_CMD_PENDING)) { kfree_skb(hdev->req_skb); - hdev->req_skb = skb_clone(hdev->sent_cmd, GFP_KERNEL); + hdev->req_skb = skb_get(hdev->sent_cmd); } return err; diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 2b41ad873c16..f502be73d8c5 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -5724,10 +5724,11 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status, hci_dev_lock(hdev); hci_store_wake_reason(hdev, bdaddr, bdaddr_type); - /* All controllers implicitly stop advertising in the event of a - * connection, so ensure that the state bit is cleared. + /* Advertising stops when a connection is created. On a failed + * connection it keeps running, so leave the state bit alone. */ - hci_dev_clear_flag(hdev, HCI_LE_ADV); + if (!status) + hci_dev_clear_flag(hdev, HCI_LE_ADV); /* Check for existing connection: * diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 8ccc3b22ef5b..f44f02ddb94e 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -5374,6 +5374,7 @@ int hci_dev_open_sync(struct hci_dev *hdev) if (hdev->req_skb) { kfree_skb(hdev->req_skb); hdev->req_skb = NULL; + hci_dev_clear_flag(hdev, HCI_CMD_PENDING); } clear_bit(HCI_RUNNING, &hdev->flags); @@ -5558,6 +5559,7 @@ int hci_dev_close_sync(struct hci_dev *hdev) if (hdev->req_skb) { kfree_skb(hdev->req_skb); hdev->req_skb = NULL; + hci_dev_clear_flag(hdev, HCI_CMD_PENDING); } clear_bit(HCI_RUNNING, &hdev->flags); diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index aa2ce78f56a2..75bfd5938b2e 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -2277,6 +2277,14 @@ static void iso_conn_ready(struct iso_conn *conn) lock_sock(parent); + /* The listener may have been closed concurrently. */ + if (parent->sk_state != BT_LISTEN || + sock_flag(parent, SOCK_ZAPPED)) { + release_sock(parent); + sock_put(parent); + return; + } + sk = iso_sock_alloc(sock_net(parent), NULL, BTPROTO_ISO, GFP_ATOMIC, 0); if (!sk) { diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 9cdfea666a2c..5d150e8623d5 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -2213,9 +2213,13 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt) BT_DBG("conn %p status 0x%02x encrypt 0x%02x", conn, status, encrypt); + rfcomm_lock(); + s = rfcomm_session_get(&conn->hdev->bdaddr, &conn->dst); - if (!s) + if (!s) { + rfcomm_unlock(); return; + } list_for_each_entry_safe(d, n, &s->dlcs, list) { if (test_and_clear_bit(RFCOMM_SEC_PENDING, &d->flags)) { @@ -2247,6 +2251,8 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt) set_bit(RFCOMM_AUTH_REJECT, &d->flags); } + rfcomm_unlock(); + rfcomm_schedule(); } diff --git a/net/ceph/osd_client.c b/net/ceph/osd_client.c index 28d76c2f6b3e..f36ce5ae7568 100644 --- a/net/ceph/osd_client.c +++ b/net/ceph/osd_client.c @@ -6,6 +6,7 @@ #include <linux/err.h> #include <linux/highmem.h> #include <linux/mm.h> +#include <linux/overflow.h> #include <linux/pagemap.h> #include <linux/slab.h> #include <linux/uaccess.h> @@ -5802,6 +5803,31 @@ static inline void convert_extent_map(struct ceph_sparse_read *sr) } #endif +static bool sparse_extent_map_valid(struct ceph_sparse_read *sr) +{ + u64 req_end, pos; + int i; + + if (check_add_overflow(sr->sr_req_off, sr->sr_req_len, &req_end)) + return false; + + pos = sr->sr_req_off; + for (i = 0; i < sr->sr_count; i++) { + struct ceph_sparse_extent *ext = &sr->sr_extent[i]; + u64 end; + + if (ext->off < pos) + return false; + if (check_add_overflow(ext->off, ext->len, &end)) + return false; + if (end > req_end) + return false; + pos = end; + } + + return true; +} + static int osd_sparse_read(struct ceph_connection *con, struct ceph_msg_data_cursor *cursor, char **pbuf) @@ -5852,6 +5878,10 @@ next_op: fallthrough; case CEPH_SPARSE_READ_DATA_LEN: convert_extent_map(sr); + if (!sparse_extent_map_valid(sr)) { + pr_warn_ratelimited("invalid sparse extent map\n"); + return -EREMOTEIO; + } ret = sizeof(sr->sr_datalen); *pbuf = (char *)&sr->sr_datalen; sr->sr_state = CEPH_SPARSE_READ_DATA_PRE; diff --git a/net/ceph/osdmap.c b/net/ceph/osdmap.c index d6282f0bcff8..cf34b35c9a90 100644 --- a/net/ceph/osdmap.c +++ b/net/ceph/osdmap.c @@ -517,6 +517,8 @@ static struct crush_map *crush_decode(void *pbyval, void *end) ceph_decode_need(p, end, 4*sizeof(u32), bad); b->id = ceph_decode_32(p); + if (b->id != -1 - i) + goto bad; b->type = ceph_decode_16(p); if (b->type == 0) goto bad; diff --git a/net/core/skbuff.c b/net/core/skbuff.c index ba3dbac80fb4..71bb4a3b5776 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -1417,10 +1417,13 @@ EXPORT_SYMBOL(skb_dump); * * Report xmit error if a device callback is tracking this skb. * skb must be freed afterwards. + * + * Does nothing for a cloned skb: the zerocopy state lives in + * skb_shinfo(), which the clones share. */ void skb_tx_error(struct sk_buff *skb) { - if (skb) { + if (skb && !skb_cloned(skb)) { skb_zcopy_downgrade_managed(skb); skb_zcopy_clear(skb, true); } diff --git a/net/core/xdp.c b/net/core/xdp.c index 0194e69da339..1d679e8fd649 100644 --- a/net/core/xdp.c +++ b/net/core/xdp.c @@ -585,7 +585,7 @@ struct xdp_frame *xdp_convert_zc_to_xdp_frame(struct xdp_buff *xdp) xdp->data - xdp->data_meta; totsize = xdp->data_end - xdp->data + metasize; - if (sizeof(*xdpf) + totsize > PAGE_SIZE) + if (sizeof(*xdpf) + totsize > SKB_WITH_OVERHEAD(PAGE_SIZE)) return NULL; page = dev_alloc_page(); @@ -602,7 +602,7 @@ struct xdp_frame *xdp_convert_zc_to_xdp_frame(struct xdp_buff *xdp) xdpf->data = addr + metasize; xdpf->len = totsize - metasize; - xdpf->headroom = 0; + xdpf->headroom = metasize; xdpf->metasize = metasize; xdpf->frame_sz = PAGE_SIZE; xdpf->mem_type = MEM_TYPE_PAGE_ORDER0; diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c index 9d114bd575f9..5b1f180485d4 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c @@ -317,7 +317,7 @@ static int ip_tunnel_bind_dev(struct net_device *dev) mtu = min(tdev->mtu, IP_MAX_MTU); } - dev->needed_headroom = t_hlen + hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(t_hlen + hlen); mtu -= t_hlen + (dev->type == ARPHRD_ETHER ? dev->hard_header_len : 0); if (mtu < IPV4_MIN_MTU) diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c index b643194f57d2..ddf62b45566b 100644 --- a/net/ipv4/ipip.c +++ b/net/ipv4/ipip.c @@ -248,7 +248,7 @@ static int ipip_tunnel_rcv(struct sk_buff *skb, u8 ipproto) tun_dst = ip_tun_rx_dst(skb, flags, 0, 0); if (!tun_dst) - return 0; + goto drop; ip_tunnel_md_udp_encap(skb, &tun_dst->u.tun_info); } skb_reset_mac_header(skb); diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index 1d9a4ac14fce..e5f2b1c6150d 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -2213,6 +2213,9 @@ int ip_mr_input(struct sk_buff *skb) if (IPCB(skb)->flags & IPSKB_FORWARDED) goto dont_forward; + if (!local) + skb_orphan(skb); + mrt = ipmr_rt_fib_lookup(net, skb); if (IS_ERR(mrt)) { kfree_skb(skb); diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index 9c677eb1d1a6..51941ad656a3 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -368,23 +368,16 @@ static void seg6_update_csum(struct sk_buff *skb) (__be32 *)addr); } -static int ipv6_srh_rcv(struct sk_buff *skb) +static int ipv6_srh_rcv(struct sk_buff *skb, struct inet6_dev *idev) { struct inet6_skb_parm *opt = IP6CB(skb); struct net *net = dev_net(skb->dev); struct ipv6_sr_hdr *hdr; - struct inet6_dev *idev; struct in6_addr *addr; int accept_seg6; hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb); - idev = __in6_dev_get(skb->dev); - if (!idev) { - kfree_skb(skb); - return -1; - } - accept_seg6 = min(READ_ONCE(net->ipv6.devconf_all->seg6_enabled), READ_ONCE(idev->cnf.seg6_enabled)); @@ -485,12 +478,11 @@ looped_back: return -1; } -static int ipv6_rpl_srh_rcv(struct sk_buff *skb) +static int ipv6_rpl_srh_rcv(struct sk_buff *skb, struct inet6_dev *idev) { struct ipv6_rpl_sr_hdr *hdr, *ohdr, *chdr; struct inet6_skb_parm *opt = IP6CB(skb); struct net *net = dev_net(skb->dev); - struct inet6_dev *idev; struct ipv6hdr *oldhdr; unsigned int chdr_len; unsigned char *buf; @@ -499,8 +491,6 @@ static int ipv6_rpl_srh_rcv(struct sk_buff *skb) u64 n = 0; u32 r; - idev = __in6_dev_get(skb->dev); - accept_rpl_seg = min(READ_ONCE(net->ipv6.devconf_all->rpl_seg_enabled), READ_ONCE(idev->cnf.rpl_seg_enabled)); if (!accept_rpl_seg) { @@ -689,10 +679,14 @@ static int ipv6_rthdr_rcv(struct sk_buff *skb) switch (hdr->type) { case IPV6_SRCRT_TYPE_4: /* segment routing */ - return ipv6_srh_rcv(skb); + if (!idev) + goto disabled; + return ipv6_srh_rcv(skb, idev); case IPV6_SRCRT_TYPE_3: /* rpl segment routing */ - return ipv6_rpl_srh_rcv(skb); + if (!idev) + goto disabled; + return ipv6_rpl_srh_rcv(skb, idev); default: break; } @@ -837,6 +831,10 @@ unknown_rh: icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->type) - skb_network_header(skb)); return -1; + +disabled: + kfree_skb_reason(skb, SKB_DROP_REASON_IPV6DISABLED); + return -1; } static const struct inet6_protocol rthdr_protocol = { diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index b843116e9b70..200d0ba1a40e 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -1137,13 +1137,11 @@ static void ip6gre_tnl_link_config_route(struct ip6_tnl *t, int set_mtu, return; if (rt->dst.dev) { - unsigned short dst_len = rt->dst.dev->hard_header_len + - t_hlen; + unsigned int headroom; - if (t->dev->header_ops) - dev->hard_header_len = dst_len; - else - dev->needed_headroom = dst_len; + headroom = rt->dst.dev->hard_header_len + t_hlen; + headroom = ip_tunnel_limit_headroom(headroom); + dev->needed_headroom = headroom; if (set_mtu) { int mtu = rt->dst.dev->mtu - t_hlen; @@ -1171,8 +1169,8 @@ static int ip6gre_calc_hlen(struct ip6_tnl *tunnel) t_hlen = tunnel->hlen + sizeof(struct ipv6hdr); - if (tunnel->dev->header_ops) - tunnel->dev->hard_header_len = LL_MAX_HEADER + t_hlen; + if (tunnel->dev->header_ops && tunnel->dev->type == ARPHRD_IP6GRE) + tunnel->dev->hard_header_len = t_hlen; else tunnel->dev->needed_headroom = LL_MAX_HEADER + t_hlen; diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c index 8972863c93ee..d332ec60f915 100644 --- a/net/ipv6/ip6_input.c +++ b/net/ipv6/ip6_input.c @@ -622,6 +622,7 @@ int ip6_mc_input(struct sk_buff *skb) if (deliver) { skb2 = skb_clone(skb, GFP_ATOMIC); } else { + skb_orphan(skb); skb2 = skb; skb = NULL; } diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index ebf83f090376..cc96bb8b706e 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1236,19 +1236,8 @@ route_lookup: */ max_headroom += LL_RESERVED_SPACE(tdev); - if (skb_headroom(skb) < max_headroom || skb_shared(skb) || - (skb_cloned(skb) && !skb_clone_writable(skb, 0))) { - struct sk_buff *new_skb; - - new_skb = skb_realloc_headroom(skb, max_headroom); - if (!new_skb) - goto tx_err_dst_release; - - if (skb->sk) - skb_set_owner_w(new_skb, skb->sk); - consume_skb(skb); - skb = new_skb; - } + if (skb_cow_head(skb, max_headroom)) + goto tx_err_dst_release; if (t->parms.collect_md) { if (t->encap.type != TUNNEL_ENCAP_NONE) @@ -1525,8 +1514,11 @@ static void ip6_tnl_link_config(struct ip6_tnl *t) tdev = __dev_get_by_index(t->net, p->link); if (tdev) { - dev->needed_headroom = tdev->hard_header_len + - tdev->needed_headroom + t_hlen; + unsigned int headroom; + + headroom = tdev->hard_header_len + tdev->needed_headroom; + headroom += t_hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(headroom); mtu = min_t(unsigned int, tdev->mtu, IP6_MAX_MTU); mtu = mtu - t_hlen; diff --git a/net/ipv6/route.c b/net/ipv6/route.c index e2056bd0df3c..a698f322db1c 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -6029,7 +6029,7 @@ static int rt6_nh_dump_exceptions(struct fib6_nh *nh, void *arg) return 0; for (i = 0; i < FIB6_EXCEPTION_BUCKET_SIZE; i++) { - hlist_for_each_entry(rt6_ex, &bucket->chain, hlist) { + hlist_for_each_entry_rcu(rt6_ex, &bucket->chain, hlist) { if (w->skip) { w->skip--; continue; diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c index 95ea0b62729a..7b5212220185 100644 --- a/net/ipv6/seg6_local.c +++ b/net/ipv6/seg6_local.c @@ -261,6 +261,15 @@ static bool decap_and_validate(struct sk_buff *skb, int proto) memset(IPCB(skb), 0, sizeof(*IPCB(skb))); IPCB(skb)->iif = iif; + } else if (proto == IPPROTO_IPV6) { + bool l3slave = ipv6_l3mdev_skb(IP6CB(skb)->flags); + int iif = IP6CB(skb)->iif; + + memset(IP6CB(skb), 0, sizeof(*IP6CB(skb))); + IP6CB(skb)->iif = iif; + IP6CB(skb)->nhoff = offsetof(struct ipv6hdr, nexthdr); + if (l3slave) + IP6CB(skb)->flags |= IP6SKB_L3SLAVE; } return true; diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c index a38b24fb8384..19b7fa8d1a2a 100644 --- a/net/ipv6/sit.c +++ b/net/ipv6/sit.c @@ -1131,7 +1131,7 @@ static void ipip6_tunnel_bind_dev(struct net_device *dev) WRITE_ONCE(dev->mtu, mtu); hlen = tdev->hard_header_len + tdev->needed_headroom; } - dev->needed_headroom = t_hlen + hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(t_hlen + hlen); } static void ipip6_tunnel_update(struct ip_tunnel *t, diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c index ea047bab65e7..4e5cc9da6e06 100644 --- a/net/iucv/af_iucv.c +++ b/net/iucv/af_iucv.c @@ -2079,6 +2079,8 @@ static int afiucv_hs_rcv(struct sk_buff *skb, struct net_device *dev, sk = NULL; read_lock(&iucv_sk_list.lock); sk_for_each(sk, &iucv_sk_list.head) { + if (iucv_sk(sk)->hs_dev != dev) + continue; if (trans_hdr->flags == AF_IUCV_FLAG_SYN) { if ((!memcmp(&iucv_sk(sk)->src_name, trans_hdr->destAppName, 8)) && diff --git a/net/l2tp/l2tp_netlink.c b/net/l2tp/l2tp_netlink.c index 59457c0c14aa..fbdd3d055dbc 100644 --- a/net/l2tp/l2tp_netlink.c +++ b/net/l2tp/l2tp_netlink.c @@ -248,8 +248,8 @@ static int l2tp_nl_cmd_tunnel_create(struct sk_buff *skb, struct genl_info *info kfree(tunnel); goto out; } - ret = l2tp_tunnel_notify(&l2tp_nl_family, info, tunnel, - L2TP_CMD_TUNNEL_CREATE); + l2tp_tunnel_notify(&l2tp_nl_family, info, tunnel, + L2TP_CMD_TUNNEL_CREATE); l2tp_tunnel_put(tunnel); out: @@ -305,8 +305,8 @@ static int l2tp_nl_cmd_tunnel_modify(struct sk_buff *skb, struct genl_info *info goto out; } - ret = l2tp_tunnel_notify(&l2tp_nl_family, info, - tunnel, L2TP_CMD_TUNNEL_MODIFY); + l2tp_tunnel_notify(&l2tp_nl_family, info, + tunnel, L2TP_CMD_TUNNEL_MODIFY); l2tp_tunnel_put(tunnel); @@ -645,8 +645,8 @@ static int l2tp_nl_cmd_session_create(struct sk_buff *skb, struct genl_info *inf session = l2tp_session_get(net, tunnel->sock, tunnel->version, tunnel_id, session_id); if (session) { - ret = l2tp_session_notify(&l2tp_nl_family, info, session, - L2TP_CMD_SESSION_CREATE); + l2tp_session_notify(&l2tp_nl_family, info, session, + L2TP_CMD_SESSION_CREATE); l2tp_session_put(session); } } @@ -710,8 +710,8 @@ static int l2tp_nl_cmd_session_modify(struct sk_buff *skb, struct genl_info *inf if (info->attrs[L2TP_ATTR_RECV_TIMEOUT]) session->reorder_timeout = nla_get_msecs(info->attrs[L2TP_ATTR_RECV_TIMEOUT]); - ret = l2tp_session_notify(&l2tp_nl_family, info, - session, L2TP_CMD_SESSION_MODIFY); + l2tp_session_notify(&l2tp_nl_family, info, + session, L2TP_CMD_SESSION_MODIFY); l2tp_session_put(session); diff --git a/net/mctp/route.c b/net/mctp/route.c index 1f3dccbb7aed..b19c63a5691a 100644 --- a/net/mctp/route.c +++ b/net/mctp/route.c @@ -998,14 +998,29 @@ int mctp_route_lookup(struct net *net, unsigned int dnet, mtu = mtu ?: rt->mtu; if (rt->dst_type == MCTP_ROUTE_DIRECT) { - mctp_eid_t saddr = mctp_dev_saddr(rt->dev); + mctp_eid_t saddr; + + /* rt->dev may be going away concurrently: its last + * reference is dropped in mctp_dev_put(), which frees + * mdev->addrs that mctp_dev_saddr() reads, and + * mctp_dst_from_route() takes a reference on it. Pin + * it before use, and skip a device that is already + * dead rather than resurrecting it. + */ + if (!refcount_inc_not_zero(&rt->dev->refs)) + break; + + saddr = mctp_dev_saddr(rt->dev); /* cannot do gateway-ed routes without a src */ - if (saddr == MCTP_ADDR_NULL && depth != 0) + if (saddr == MCTP_ADDR_NULL && depth != 0) { + mctp_dev_put(rt->dev); break; + } if (dst) mctp_dst_from_route(dst, daddr, saddr, mtu, rt); + mctp_dev_put(rt->dev); rc = 0; break; diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c index 961be5054a03..17b78dcbf8ab 100644 --- a/net/mpls/af_mpls.c +++ b/net/mpls/af_mpls.c @@ -221,6 +221,7 @@ static u32 mpls_multipath_hash(struct mpls_route *rt, struct sk_buff *skb) if (pskb_may_pull(skb, mpls_hdr_len + sizeof(struct iphdr))) { const struct iphdr *v4hdr; + hdr = mpls_hdr(skb) + label_index; v4hdr = (const struct iphdr *)(hdr + 1); if (v4hdr->version == 4) { hash = jhash_3words(ntohl(v4hdr->saddr), @@ -231,6 +232,7 @@ static u32 mpls_multipath_hash(struct mpls_route *rt, struct sk_buff *skb) sizeof(struct ipv6hdr))) { const struct ipv6hdr *v6hdr; + hdr = mpls_hdr(skb) + label_index; v6hdr = (const struct ipv6hdr *)(hdr + 1); hash = __ipv6_addr_jhash(&v6hdr->saddr, hash); hash = __ipv6_addr_jhash(&v6hdr->daddr, hash); diff --git a/net/mptcp/syncookies.c b/net/mptcp/syncookies.c index 7f22526346a7..b5cac5701122 100644 --- a/net/mptcp/syncookies.c +++ b/net/mptcp/syncookies.c @@ -118,6 +118,7 @@ bool mptcp_token_join_cookie_init_state(struct mptcp_subflow_request_sock *subfl subflow_req->local_nonce = e->local_nonce; subflow_req->backup = e->backup; subflow_req->remote_id = e->join_id; + subflow_req->local_id = e->local_id; subflow_req->token = e->token; subflow_req->msk = msk; spin_unlock_bh(&join_entry_locks[i]); diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index 95697d4e16e6..37c90580ecb8 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -933,10 +933,14 @@ static int ovs_ct_check_limit(struct net *net, const struct ovs_conntrack_info *info) { struct ovs_net *ovs_net = net_generic(net, ovs_net_id); - const struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info; + const struct ovs_ct_limit_info *ct_limit_info; u32 per_zone_limit, connections; u32 conncount_key; + ct_limit_info = rcu_dereference(ovs_net->ct_limit_info); + if (!ct_limit_info) + return 0; + conncount_key = info->zone.id; per_zone_limit = ct_limit_get(ct_limit_info, info->zone.id); @@ -1585,40 +1589,55 @@ static void __ovs_ct_free_action(struct ovs_conntrack_info *ct_info) #if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT) static int ovs_ct_limit_init(struct net *net, struct ovs_net *ovs_net) { + struct ovs_ct_limit_info *info; int i, err; - ovs_net->ct_limit_info = kmalloc_obj(*ovs_net->ct_limit_info); - if (!ovs_net->ct_limit_info) + info = kmalloc_obj(*info); + if (!info) return -ENOMEM; - ovs_net->ct_limit_info->default_limit = OVS_CT_LIMIT_DEFAULT; - ovs_net->ct_limit_info->limits = + info->default_limit = OVS_CT_LIMIT_DEFAULT; + info->limits = kmalloc_objs(struct hlist_head, CT_LIMIT_HASH_BUCKETS); - if (!ovs_net->ct_limit_info->limits) { - kfree(ovs_net->ct_limit_info); + if (!info->limits) { + kfree(info); return -ENOMEM; } for (i = 0; i < CT_LIMIT_HASH_BUCKETS; i++) - INIT_HLIST_HEAD(&ovs_net->ct_limit_info->limits[i]); + INIT_HLIST_HEAD(&info->limits[i]); - ovs_net->ct_limit_info->data = nf_conncount_init(net, sizeof(u32)); + info->data = nf_conncount_init(net, sizeof(u32)); - if (IS_ERR(ovs_net->ct_limit_info->data)) { - err = PTR_ERR(ovs_net->ct_limit_info->data); - kfree(ovs_net->ct_limit_info->limits); - kfree(ovs_net->ct_limit_info); + if (IS_ERR(info->data)) { + err = PTR_ERR(info->data); + kfree(info->limits); + kfree(info); pr_err("openvswitch: failed to init nf_conncount %d\n", err); return err; } + rcu_assign_pointer(ovs_net->ct_limit_info, info); return 0; } -static void ovs_ct_limit_exit(struct net *net, struct ovs_net *ovs_net) +static void *ovs_ct_limit_exit_start(struct ovs_net *ovs_net) { - const struct ovs_ct_limit_info *info = ovs_net->ct_limit_info; + return rcu_replace_pointer(ovs_net->ct_limit_info, NULL, + lockdep_ovsl_is_held()); +} + +/* The CT limit state must be detached by ovs_ct_limit_exit_start() and an + * RCU grace period must elapse before this function runs. The pernet core + * guarantees the grace period between the .pre_exit and .exit callbacks. + */ +static void ovs_ct_limit_exit_finish(struct net *net, void *data) +{ + const struct ovs_ct_limit_info *info = data; int i; + if (!info) + return; + nf_conncount_destroy(net, info->data); for (i = 0; i < CT_LIMIT_HASH_BUCKETS; ++i) { struct hlist_head *head = &info->limits[i]; @@ -1626,7 +1645,7 @@ static void ovs_ct_limit_exit(struct net *net, struct ovs_net *ovs_net) struct hlist_node *next; hlist_for_each_entry_safe(ct_limit, next, head, hlist_node) - kfree_rcu(ct_limit, rcu); + kfree(ct_limit); } kfree(info->limits); kfree(info); @@ -1665,12 +1684,13 @@ static bool check_zone_id(int zone_id, u16 *pzone) return false; } -static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit, - struct ovs_ct_limit_info *info) +static int ovs_ct_limit_set_zone_limit(struct ovs_net *ovs_net, + struct nlattr *nla_zone_limit) { struct ovs_zone_limit *zone_limit; - int rem; + struct ovs_ct_limit_info *info; u16 zone; + int rem; rem = NLA_ALIGN(nla_len(nla_zone_limit)); zone_limit = (struct ovs_zone_limit *)nla_data(nla_zone_limit); @@ -1679,6 +1699,7 @@ static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit, if (unlikely(zone_limit->zone_id == OVS_ZONE_LIMIT_DEFAULT_ZONE)) { ovs_lock(); + info = ovsl_dereference(ovs_net->ct_limit_info); info->default_limit = zone_limit->limit; ovs_unlock(); } else if (unlikely(!check_zone_id( @@ -1695,6 +1716,7 @@ static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit, ct_limit->limit = zone_limit->limit; ovs_lock(); + info = ovsl_dereference(ovs_net->ct_limit_info); ct_limit_set(info, ct_limit); ovs_unlock(); } @@ -1709,12 +1731,13 @@ static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit, return 0; } -static int ovs_ct_limit_del_zone_limit(struct nlattr *nla_zone_limit, - struct ovs_ct_limit_info *info) +static int ovs_ct_limit_del_zone_limit(struct ovs_net *ovs_net, + struct nlattr *nla_zone_limit) { struct ovs_zone_limit *zone_limit; - int rem; + struct ovs_ct_limit_info *info; u16 zone; + int rem; rem = NLA_ALIGN(nla_len(nla_zone_limit)); zone_limit = (struct ovs_zone_limit *)nla_data(nla_zone_limit); @@ -1723,6 +1746,7 @@ static int ovs_ct_limit_del_zone_limit(struct nlattr *nla_zone_limit, if (unlikely(zone_limit->zone_id == OVS_ZONE_LIMIT_DEFAULT_ZONE)) { ovs_lock(); + info = ovsl_dereference(ovs_net->ct_limit_info); info->default_limit = OVS_CT_LIMIT_DEFAULT; ovs_unlock(); } else if (unlikely(!check_zone_id( @@ -1730,6 +1754,7 @@ static int ovs_ct_limit_del_zone_limit(struct nlattr *nla_zone_limit, OVS_NLERR(true, "zone id is out of range"); } else { ovs_lock(); + info = ovsl_dereference(ovs_net->ct_limit_info); ct_limit_del(info, zone); ovs_unlock(); } @@ -1773,6 +1798,7 @@ static int __ovs_ct_limit_get_zone_limit(struct net *net, return nla_put_nohdr(reply, sizeof(zone_limit), &zone_limit); } +/* Called with RCU read lock held. */ static int ovs_ct_limit_get_zone_limit(struct net *net, struct nlattr *nla_zone_limit, struct ovs_ct_limit_info *info, @@ -1796,12 +1822,10 @@ static int ovs_ct_limit_get_zone_limit(struct net *net, &zone))) { OVS_NLERR(true, "zone id is out of range"); } else { - rcu_read_lock(); limit = ct_limit_get(info, zone); err = __ovs_ct_limit_get_zone_limit( net, info->data, zone, limit, reply); - rcu_read_unlock(); if (err) return err; } @@ -1816,6 +1840,7 @@ static int ovs_ct_limit_get_zone_limit(struct net *net, return 0; } +/* Called with RCU read lock held. */ static int ovs_ct_limit_get_all_zone_limit(struct net *net, struct ovs_ct_limit_info *info, struct sk_buff *reply) @@ -1828,19 +1853,16 @@ static int ovs_ct_limit_get_all_zone_limit(struct net *net, if (err) return err; - rcu_read_lock(); for (i = 0; i < CT_LIMIT_HASH_BUCKETS; ++i) { head = &info->limits[i]; hlist_for_each_entry_rcu(ct_limit, head, hlist_node) { err = __ovs_ct_limit_get_zone_limit(net, info->data, ct_limit->zone, ct_limit->limit, reply); if (err) - goto exit_err; + return err; } } -exit_err: - rcu_read_unlock(); return err; } @@ -1850,7 +1872,6 @@ static int ovs_ct_limit_cmd_set(struct sk_buff *skb, struct genl_info *info) struct sk_buff *reply; struct ovs_header *ovs_reply_header; struct ovs_net *ovs_net = net_generic(sock_net(skb->sk), ovs_net_id); - struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info; int err; reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_SET, @@ -1863,8 +1884,8 @@ static int ovs_ct_limit_cmd_set(struct sk_buff *skb, struct genl_info *info) goto exit_err; } - err = ovs_ct_limit_set_zone_limit(a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT], - ct_limit_info); + err = ovs_ct_limit_set_zone_limit(ovs_net, + a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT]); if (err) goto exit_err; @@ -1884,7 +1905,6 @@ static int ovs_ct_limit_cmd_del(struct sk_buff *skb, struct genl_info *info) struct sk_buff *reply; struct ovs_header *ovs_reply_header; struct ovs_net *ovs_net = net_generic(sock_net(skb->sk), ovs_net_id); - struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info; int err; reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_DEL, @@ -1897,8 +1917,8 @@ static int ovs_ct_limit_cmd_del(struct sk_buff *skb, struct genl_info *info) goto exit_err; } - err = ovs_ct_limit_del_zone_limit(a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT], - ct_limit_info); + err = ovs_ct_limit_del_zone_limit(ovs_net, + a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT]); if (err) goto exit_err; @@ -1918,7 +1938,7 @@ static int ovs_ct_limit_cmd_get(struct sk_buff *skb, struct genl_info *info) struct ovs_header *ovs_reply_header; struct net *net = sock_net(skb->sk); struct ovs_net *ovs_net = net_generic(net, ovs_net_id); - struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info; + struct ovs_ct_limit_info *ct_limit_info; int err; reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_GET, @@ -1932,18 +1952,19 @@ static int ovs_ct_limit_cmd_get(struct sk_buff *skb, struct genl_info *info) goto exit_err; } + rcu_read_lock(); + ct_limit_info = rcu_dereference(ovs_net->ct_limit_info); if (a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT]) { err = ovs_ct_limit_get_zone_limit( net, a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT], ct_limit_info, reply); - if (err) - goto exit_err; } else { err = ovs_ct_limit_get_all_zone_limit(net, ct_limit_info, reply); - if (err) - goto exit_err; } + rcu_read_unlock(); + if (err) + goto exit_err; nla_nest_end(reply, nla_reply); genlmsg_end(reply, ovs_reply_header); @@ -2001,6 +2022,7 @@ int ovs_ct_init(struct net *net) { unsigned int n_bits = sizeof(struct ovs_key_ct_labels) * BITS_PER_BYTE; struct ovs_net *ovs_net = net_generic(net, ovs_net_id); + int err = 0; if (nf_connlabels_get(net, n_bits - 1)) { ovs_net->xt_label = false; @@ -2010,18 +2032,36 @@ int ovs_ct_init(struct net *net) } #if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT) - return ovs_ct_limit_init(net, ovs_net); -#else - return 0; + err = ovs_ct_limit_init(net, ovs_net); + if (err && ovs_net->xt_label) + nf_connlabels_put(net); #endif + return err; } -void ovs_ct_exit(struct net *net) +/* Must be called with ovs_mutex held. Detaches the RCU-protected + * ct_limit_info and stores it in ovs_net->ct_limit_exit_data for + * ovs_ct_exit_finish() to complete the teardown after an RCU grace period. + */ +void ovs_ct_exit_start(struct net *net __maybe_unused) +{ +#if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT) + struct ovs_net *ovs_net = net_generic(net, ovs_net_id); + + ovs_net->ct_limit_exit_data = ovs_ct_limit_exit_start(ovs_net); +#endif +} + +/* Completes the CT limit teardown. The pernet core guarantees an RCU + * grace period between detaching the state in ovs_ct_exit_start() and + * this call, so no RCU readers remain. + */ +void ovs_ct_exit_finish(struct net *net) { struct ovs_net *ovs_net = net_generic(net, ovs_net_id); #if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT) - ovs_ct_limit_exit(net, ovs_net); + ovs_ct_limit_exit_finish(net, ovs_net->ct_limit_exit_data); #endif if (ovs_net->xt_label) diff --git a/net/openvswitch/conntrack.h b/net/openvswitch/conntrack.h index 317e525c8a11..ab21d032fde3 100644 --- a/net/openvswitch/conntrack.h +++ b/net/openvswitch/conntrack.h @@ -14,7 +14,8 @@ enum ovs_key_attr; #if IS_ENABLED(CONFIG_NF_CONNTRACK) int ovs_ct_init(struct net *); -void ovs_ct_exit(struct net *); +void ovs_ct_exit_start(struct net *net); +void ovs_ct_exit_finish(struct net *net); bool ovs_ct_verify(struct net *, enum ovs_key_attr attr); int ovs_ct_copy_action(struct net *, const struct nlattr *, const struct sw_flow_key *, struct sw_flow_actions **, @@ -40,7 +41,8 @@ void ovs_ct_free_action(const struct nlattr *a); static inline int ovs_ct_init(struct net *net) { return 0; } -static inline void ovs_ct_exit(struct net *net) { } +static inline void ovs_ct_exit_start(struct net *net) { } +static inline void ovs_ct_exit_finish(struct net *net) { } static inline bool ovs_ct_verify(struct net *net, int attr) { diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index ae69b2cabab9..b601e4859de9 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -285,6 +285,7 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key) consume_skb(skb); break; default: + skb_tx_error(skb); kfree_skb(skb); break; } @@ -601,8 +602,6 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb, err = genlmsg_unicast(ovs_dp_get_net(dp), user_skb, upcall_info->portid); user_skb = NULL; out: - if (err) - skb_tx_error(skb); consume_skb(user_skb); consume_skb(nskb); @@ -1473,33 +1472,34 @@ static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info *info) goto unlock; } - ovs_flow_tbl_remove(&dp->table, flow); - ovs_unlock(); - - reply = ovs_flow_cmd_alloc_info((const struct sw_flow_actions __force *) flow->sf_acts, + reply = ovs_flow_cmd_alloc_info(ovsl_dereference(flow->sf_acts), &flow->id, info, false, ufid_flags); - if (likely(reply)) { - if (!IS_ERR(reply)) { - rcu_read_lock(); /*To keep RCU checker happy. */ - err = ovs_flow_cmd_fill_info(flow, ovs_header->dp_ifindex, - reply, info->snd_portid, - info->snd_seq, 0, - OVS_FLOW_CMD_DEL, - ufid_flags); - rcu_read_unlock(); - if (WARN_ON_ONCE(err < 0)) { - kfree_skb(reply); - goto out_free; - } + if (IS_ERR(reply)) { + netlink_set_err(sock_net(skb->sk)->genl_sock, 0, 0, + PTR_ERR(reply)); + reply = NULL; + } - ovs_notify(&dp_flow_genl_family, reply, info); - } else { - netlink_set_err(sock_net(skb->sk)->genl_sock, 0, 0, - PTR_ERR(reply)); + if (likely(reply)) { + err = ovs_flow_cmd_fill_info(flow, ovs_header->dp_ifindex, + reply, info->snd_portid, + info->snd_seq, 0, + OVS_FLOW_CMD_DEL, ufid_flags); + if (WARN_ON_ONCE(err < 0)) { + kfree_skb(reply); + reply = NULL; } } + /* Removal has to happen after ovs_flow_cmd_fill_info(), as it uses + * the flow->mask that can be scheduled to be freed by the + * ovs_flow_tbl_remove() and we're not holding the RCU read lock. + */ + ovs_flow_tbl_remove(&dp->table, flow); + ovs_unlock(); + + if (likely(reply)) + ovs_notify(&dp_flow_genl_family, reply, info); -out_free: ovs_flow_free(flow, true); return 0; unlock: @@ -2756,6 +2756,13 @@ static void __net_exit list_vports_from_net(struct net *net, struct net *dnet, } } +static void __net_exit ovs_pre_exit_net(struct net *dnet) +{ + ovs_lock(); + ovs_ct_exit_start(dnet); + ovs_unlock(); +} + static void __net_exit ovs_exit_net(struct net *dnet) { struct datapath *dp, *dp_next; @@ -2766,7 +2773,7 @@ static void __net_exit ovs_exit_net(struct net *dnet) ovs_lock(); - ovs_ct_exit(dnet); + ovs_ct_exit_finish(dnet); list_for_each_entry_safe(dp, dp_next, &ovs_net->dps, list_node) __dp_destroy(dp); @@ -2790,6 +2797,7 @@ static void __net_exit ovs_exit_net(struct net *dnet) static struct pernet_operations ovs_net_ops = { .init = ovs_init_net, + .pre_exit = ovs_pre_exit_net, .exit = ovs_exit_net, .id = &ovs_net_id, .size = sizeof(struct ovs_net), diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h index 696640e88fa7..b2c2b8da12d4 100644 --- a/net/openvswitch/datapath.h +++ b/net/openvswitch/datapath.h @@ -164,7 +164,10 @@ struct dp_upcall_info { * Protected by genl_mutex. * @dp_notify_work: A work notifier to handle port unregistering. * @masks_rebalance: A work to periodically optimize flow table caches. - * @ct_limit_info: A hash table of conntrack zone connection limits. + * @ct_limit_info: Hash table of conntrack zone connection limits. Protected + * by RCU; updates and teardown are serialized by ovs_mutex. May be NULL during + * netns teardown. + * @ct_limit_exit_data: CT limit state detached at .pre_exit, freed at .exit. * @xt_label: Whether connlables are configured for the network or not. */ struct ovs_net { @@ -172,7 +175,8 @@ struct ovs_net { struct work_struct dp_notify_work; struct delayed_work masks_rebalance; #if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT) - struct ovs_ct_limit_info *ct_limit_info; + struct ovs_ct_limit_info __rcu *ct_limit_info; + struct ovs_ct_limit_info *ct_limit_exit_data; #endif bool xt_label; }; diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index ef2b4bf51564..4539dc2c6d38 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -1278,7 +1278,7 @@ static void transition_one_qdisc(struct net_device *dev, rcu_assign_pointer(dev_queue->qdisc, new_qdisc); if (need_watchdog_p) { - WRITE_ONCE(dev_queue->trans_start, 0); + WRITE_ONCE(dev_queue->trans_start, jiffies); *need_watchdog_p = 1; } } diff --git a/net/sctp/inqueue.c b/net/sctp/inqueue.c index a024c0843247..5f988b3a8814 100644 --- a/net/sctp/inqueue.c +++ b/net/sctp/inqueue.c @@ -71,8 +71,11 @@ void sctp_inq_free(struct sctp_inq *queue) */ void sctp_inq_push(struct sctp_inq *q, struct sctp_chunk *chunk) { - /* Directly call the packet handling routine. */ - if (chunk->rcvr->dead) { + /* Directly call the packet handling routine. Drop the chunk if the + * receiver or the transport it was looked up on is gone. + */ + if (chunk->rcvr->dead || + (chunk->transport && chunk->transport->dead)) { sctp_chunk_free(chunk); return; } diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c index 424f10a6fdba..94716406d602 100644 --- a/net/sctp/sm_sideeffect.c +++ b/net/sctp/sm_sideeffect.c @@ -1332,6 +1332,10 @@ static int sctp_cmd_interpreter(enum sctp_event_type event_type, sctp_outq_uncork(&asoc->outqueue, gfp); local_cork = 0; } + /* No chunk left in this packet may use this asoc. */ + if (event_type == SCTP_EVENT_T_CHUNK && + chunk->asoc == asoc) + chunk->pdiscard = 1; /* Delete the current association. */ sctp_cmd_delete_tcb(commands, asoc); asoc = NULL; diff --git a/net/sctp/stream.c b/net/sctp/stream.c index 34ffe6c945a4..e4354d58f20b 100644 --- a/net/sctp/stream.c +++ b/net/sctp/stream.c @@ -22,6 +22,15 @@ #include <net/sctp/sm.h> #include <net/sctp/stream_sched.h> +#define SCTP_STRRESET_MASK(type) \ + BIT(ntohs(type) - ntohs(SCTP_PARAM_RESET_OUT_REQUEST)) +#define SCTP_STRRESET_TEST(asoc, type) \ + ((asoc)->strreset_outstanding & SCTP_STRRESET_MASK(type)) +#define SCTP_STRRESET_SET(asoc, type) \ + ((asoc)->strreset_outstanding |= SCTP_STRRESET_MASK(type)) +#define SCTP_STRRESET_CLEAR(asoc, type) \ + ((asoc)->strreset_outstanding &= ~SCTP_STRRESET_MASK(type)) + static void sctp_stream_shrink_out(struct sctp_stream *stream, __u16 outcnt) { struct sctp_association *asoc; @@ -372,7 +381,10 @@ int sctp_send_reset_streams(struct sctp_association *asoc, goto out; } - asoc->strreset_outstanding = out + in; + if (out) + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_OUT_REQUEST); + if (in) + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_IN_REQUEST); out: return retval; @@ -417,7 +429,7 @@ int sctp_send_reset_assoc(struct sctp_association *asoc) return retval; } - asoc->strreset_outstanding = 1; + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_TSN_REQUEST); return 0; } @@ -474,7 +486,10 @@ int sctp_send_add_streams(struct sctp_association *asoc, goto out; } - asoc->strreset_outstanding = !!out + !!in; + if (out) + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_ADD_OUT_STREAMS); + if (in) + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_ADD_IN_STREAMS); out: return retval; @@ -482,13 +497,13 @@ out: static struct sctp_paramhdr *sctp_chunk_lookup_strreset_param( struct sctp_association *asoc, __be32 resp_seq, - __be16 type) + __be16 type, bool match_seq) { struct sctp_chunk *chunk = asoc->strreset_chunk; struct sctp_reconf_chunk *hdr; union sctp_params param; - if (!chunk) + if (!chunk || !chunk->transport) return NULL; hdr = (struct sctp_reconf_chunk *)chunk->chunk_hdr; @@ -499,7 +514,7 @@ static struct sctp_paramhdr *sctp_chunk_lookup_strreset_param( */ struct sctp_strreset_tsnreq *req = param.v; - if ((!resp_seq || req->request_seq == resp_seq) && + if ((!match_seq || req->request_seq == resp_seq) && (!type || type == req->param_hdr.type)) return param.v; } @@ -564,13 +579,14 @@ struct sctp_chunk *sctp_process_strreset_outreq( if (asoc->strreset_chunk) { if (!sctp_chunk_lookup_strreset_param( asoc, outreq->response_seq, - SCTP_PARAM_RESET_IN_REQUEST)) { + SCTP_PARAM_RESET_IN_REQUEST, true) || + !SCTP_STRRESET_TEST(asoc, SCTP_PARAM_RESET_IN_REQUEST)) { /* same process with outstanding isn't 0 */ result = SCTP_STRRESET_ERR_IN_PROGRESS; goto out; } - asoc->strreset_outstanding--; + SCTP_STRRESET_CLEAR(asoc, SCTP_PARAM_RESET_IN_REQUEST); asoc->strreset_outseq++; if (!asoc->strreset_outstanding) { @@ -669,7 +685,7 @@ struct sctp_chunk *sctp_process_strreset_inreq( SCTP_SO(stream, i)->state = SCTP_STREAM_CLOSED; asoc->strreset_chunk = chunk; - asoc->strreset_outstanding = 1; + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_OUT_REQUEST); sctp_chunk_hold(asoc->strreset_chunk); result = SCTP_STRRESET_PERFORMED; @@ -816,13 +832,14 @@ struct sctp_chunk *sctp_process_strreset_addstrm_out( if (asoc->strreset_chunk) { if (!sctp_chunk_lookup_strreset_param( - asoc, 0, SCTP_PARAM_RESET_ADD_IN_STREAMS)) { + asoc, 0, SCTP_PARAM_RESET_ADD_IN_STREAMS, false) || + !SCTP_STRRESET_TEST(asoc, SCTP_PARAM_RESET_ADD_IN_STREAMS)) { /* same process with outstanding isn't 0 */ result = SCTP_STRRESET_ERR_IN_PROGRESS; goto out; } - asoc->strreset_outstanding--; + SCTP_STRRESET_CLEAR(asoc, SCTP_PARAM_RESET_ADD_IN_STREAMS); asoc->strreset_outseq++; if (!asoc->strreset_outstanding) { @@ -899,7 +916,7 @@ struct sctp_chunk *sctp_process_strreset_addstrm_in( goto out; asoc->strreset_chunk = chunk; - asoc->strreset_outstanding = 1; + SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_ADD_OUT_STREAMS); sctp_chunk_hold(asoc->strreset_chunk); stream->outcnt = outcnt; @@ -927,8 +944,9 @@ struct sctp_chunk *sctp_process_strreset_resp( struct sctp_paramhdr *req; __u32 result; - req = sctp_chunk_lookup_strreset_param(asoc, resp->response_seq, 0); - if (!req) + req = sctp_chunk_lookup_strreset_param(asoc, resp->response_seq, 0, + true); + if (!req || !SCTP_STRRESET_TEST(asoc, req->type)) return NULL; result = ntohl(resp->result); @@ -1078,7 +1096,7 @@ struct sctp_chunk *sctp_process_strreset_resp( nums, 0, GFP_ATOMIC); } - asoc->strreset_outstanding--; + SCTP_STRRESET_CLEAR(asoc, req->type); asoc->strreset_outseq++; /* remove everything for this reconf request */ diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c index 00403175b740..cff910cedbfc 100644 --- a/net/smc/af_smc.c +++ b/net/smc/af_smc.c @@ -3233,7 +3233,8 @@ int smc_ioctl(struct socket *sock, unsigned int cmd, return -EINVAL; } if (smc->sk.sk_state == SMC_INIT || - smc->sk.sk_state == SMC_CLOSED) + smc->sk.sk_state == SMC_CLOSED || + !READ_ONCE(smc->conn.sndbuf_desc)) answ = 0; else answ = smc->conn.sndbuf_desc->len - diff --git a/net/smc/smc.h b/net/smc/smc.h index 52145df83f6e..427b6d63b993 100644 --- a/net/smc/smc.h +++ b/net/smc/smc.h @@ -277,9 +277,9 @@ struct smc_connection { * 0 for SMC-R, 32 for SMC-D */ u64 peer_token; /* SMC-D token of peer */ - u8 killed : 1; /* abnormal termination */ - u8 freed : 1; /* normal termination */ - u8 out_of_sync : 1; /* out of sync with peer */ + u8 killed; /* abnormal termination */ + u8 freed; /* normal termination */ + u8 out_of_sync; /* out of sync with peer */ }; struct smc_sock { /* smc sock container */ diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c index b4208cb186c5..04aedd957543 100644 --- a/net/smc/smc_core.c +++ b/net/smc/smc_core.c @@ -1148,13 +1148,13 @@ again: read_unlock_bh(&lgr->conns_lock); /* pre-fetch buffer outside of send_lock, might sleep */ rc = smc_cdc_get_free_slot(conn, to_lnk, &wr_buf, NULL, &pend); - if (rc) - goto err_out; - /* avoid race with smcr_tx_sndbuf_nonempty() */ - spin_lock_bh(&conn->send_lock); - smc_switch_link_and_count(conn, to_lnk); - rc = smc_switch_cursor(smc, pend, wr_buf); - spin_unlock_bh(&conn->send_lock); + if (!rc) { + /* avoid race with smcr_tx_sndbuf_nonempty() */ + spin_lock_bh(&conn->send_lock); + smc_switch_link_and_count(conn, to_lnk); + rc = smc_switch_cursor(smc, pend, wr_buf); + spin_unlock_bh(&conn->send_lock); + } sock_put(&smc->sk); if (rc) goto err_out; @@ -1209,14 +1209,16 @@ static void smcd_buf_detach(struct smc_connection *conn) { struct smcd_dev *smcd = conn->lgr->smcd; u64 peer_token = conn->peer_token; + struct smc_buf_desc *buf_desc; if (!conn->sndbuf_desc) return; smc_ism_detach_dmb(smcd, peer_token); - kfree(conn->sndbuf_desc); + buf_desc = conn->sndbuf_desc; conn->sndbuf_desc = NULL; + kfree(buf_desc); } static void smc_buf_unuse(struct smc_connection *conn, @@ -1268,11 +1270,10 @@ void smc_conn_free(struct smc_connection *conn) goto lgr_put; if (lgr->is_smcd) { - if (!list_empty(&lgr->list)) - smc_ism_unset_conn(conn); + smc_ism_unset_conn(conn); + tasklet_kill(&conn->rx_tsklet); if (smc_ism_support_dmb_nocopy(lgr->smcd)) smcd_buf_detach(conn); - tasklet_kill(&conn->rx_tsklet); } else { smc_cdc_wait_pend_tx_wr(conn); if (current_work() != &conn->abort_work) @@ -1525,12 +1526,12 @@ static void smc_conn_kill(struct smc_connection *conn, bool soft) smc_sk_wake_ups(smc); if (conn->lgr->is_smcd) { smc_ism_unset_conn(conn); - if (smc_ism_support_dmb_nocopy(conn->lgr->smcd)) - smcd_buf_detach(conn); if (soft) tasklet_kill(&conn->rx_tsklet); else tasklet_unlock_wait(&conn->rx_tsklet); + if (smc_ism_support_dmb_nocopy(conn->lgr->smcd)) + smcd_buf_detach(conn); } else { smc_cdc_wait_pend_tx_wr(conn); } diff --git a/net/smc/smc_llc.c b/net/smc/smc_llc.c index aa6d83af55ed..481b0495b0d3 100644 --- a/net/smc/smc_llc.c +++ b/net/smc/smc_llc.c @@ -157,6 +157,7 @@ struct smc_llc_msg_confirm_rkey { /* type 0x06 */ }; #define SMC_LLC_DEL_RKEY_MAX 8 +#define SMC_LLC_DEL_RKEY_V2_INLINE 9 #define SMC_LLC_FLAG_RKEY_RETRY 0x10 #define SMC_LLC_FLAG_RKEY_NEG 0x20 @@ -177,6 +178,15 @@ struct smc_llc_msg_delete_rkey_v2 { /* type 0x29 */ __be32 rkey[]; }; +/* the leading rkeys of a DELETE_RKEY_V2 fit into union smc_llc_msg */ +struct smc_llc_msg_delete_rkey_v2_inline { /* type 0x29 */ + struct smc_llc_hdr hd; + u8 num_rkeys; + u8 num_inval_rkeys; + u8 reserved[2]; + __be32 rkey[SMC_LLC_DEL_RKEY_V2_INLINE]; +}; + union smc_llc_msg { struct smc_llc_msg_confirm_link confirm_link; struct smc_llc_msg_add_link add_link; @@ -186,6 +196,7 @@ union smc_llc_msg { struct smc_llc_msg_confirm_rkey confirm_rkey; struct smc_llc_msg_delete_rkey delete_rkey; + struct smc_llc_msg_delete_rkey_v2_inline delete_rkey_v2; struct smc_llc_msg_test_link test_link; struct { @@ -194,15 +205,25 @@ union smc_llc_msg { } raw; }; +static_assert(SMC_LLC_DEL_RKEY_V2_INLINE == + (sizeof(union smc_llc_msg) - + offsetof(struct smc_llc_msg_delete_rkey_v2, rkey)) / + sizeof(__be32)); +static_assert(offsetof(struct smc_llc_msg_delete_rkey_v2_inline, rkey) == + offsetof(struct smc_llc_msg_delete_rkey_v2, rkey)); + #define SMC_LLC_FLAG_RESP 0x80 struct smc_llc_qentry { struct list_head list; struct smc_link *link; + u16 body_len; union smc_llc_msg msg; + u8 body[] __counted_by(body_len); }; -static void smc_llc_enqueue(struct smc_link *link, union smc_llc_msg *llc); +static void smc_llc_enqueue(struct smc_link *link, union smc_llc_msg *llc, + u32 byte_len); struct smc_llc_qentry *smc_llc_flow_qentry_clr(struct smc_llc_flow *flow) { @@ -998,15 +1019,20 @@ static int smc_llc_cli_conf_link(struct smc_link *link, static void smc_llc_save_add_link_rkeys(struct smc_link *link, struct smc_link *link_new, - u8 *llc_msg) + struct smc_llc_qentry *qentry) { + const u32 rt_off = offsetof(struct smc_llc_msg_add_link_v2_ext, rt); struct smc_llc_msg_add_link_v2_ext *ext; struct smc_link_group *lgr = link->lgr; int max, i; - ext = (struct smc_llc_msg_add_link_v2_ext *)(llc_msg + - SMC_WR_TX_SIZE); + /* the rkey count itself is only there if enough bytes arrived */ + if (qentry->body_len < rt_off) + return; + ext = (struct smc_llc_msg_add_link_v2_ext *)qentry->body; max = min_t(u8, ext->num_rkeys, SMC_LLC_RKEYS_PER_MSG_V2); + max = min_t(u32, max, (qentry->body_len - rt_off) / + sizeof(ext->rt[0])); down_write(&lgr->rmbs_lock); for (i = 0; i < max; i++) { smc_rtoken_set(lgr, link->link_idx, link_new->link_idx, @@ -1099,9 +1125,7 @@ int smc_llc_cli_add_link(struct smc_link *link, struct smc_llc_qentry *qentry) if (rc) goto out_clear_lnk; if (lgr->smc_version == SMC_V2) { - u8 *llc_msg = smc_link_shared_v2_rxbuf(link) ? - (u8 *)lgr->wr_rx_buf_v2 : (u8 *)llc; - smc_llc_save_add_link_rkeys(link, lnk_new, llc_msg); + smc_llc_save_add_link_rkeys(link, lnk_new, qentry); } else { rc = smc_llc_cli_rkey_exchange(link, lnk_new); if (rc) { @@ -1481,7 +1505,7 @@ int smc_llc_srv_add_link(struct smc_link *link, } add_llc = &qentry->msg.add_link; if (add_llc->hd.flags & SMC_LLC_FLAG_ADD_LNK_REJ) { - smc_llc_flow_qentry_del(&lgr->llc_flow_lcl); + smc_llc_flow_qentry_clr(&lgr->llc_flow_lcl); rc = -ENOLINK; goto out_err; } @@ -1492,7 +1516,8 @@ int smc_llc_srv_add_link(struct smc_link *link, lgr_new_t = SMC_LGR_ASYMMETRIC_PEER; } smc_llc_save_add_link_info(link_new, add_llc); - smc_llc_flow_qentry_del(&lgr->llc_flow_lcl); + /* add_llc still points into qentry, so only detach it here */ + smc_llc_flow_qentry_clr(&lgr->llc_flow_lcl); rc = smc_ib_ready_link(link_new); if (rc) @@ -1501,9 +1526,7 @@ int smc_llc_srv_add_link(struct smc_link *link, if (rc) goto out_err; if (lgr->smc_version == SMC_V2) { - u8 *llc_msg = smc_link_shared_v2_rxbuf(link) ? - (u8 *)lgr->wr_rx_buf_v2 : (u8 *)add_llc; - smc_llc_save_add_link_rkeys(link, link_new, llc_msg); + smc_llc_save_add_link_rkeys(link, link_new, qentry); } else { rc = smc_llc_srv_rkey_exchange(link, link_new); if (rc) @@ -1512,14 +1535,14 @@ int smc_llc_srv_add_link(struct smc_link *link, rc = smc_llc_srv_conf_link(link, link_new, lgr_new_t); if (rc) goto out_err; - kfree(ini); - return 0; + goto out; out_err: if (link_new) { link_new->state = SMC_LNK_INACTIVE; smcr_link_clear(link_new, false); } out: + kfree(qentry); kfree(ini); if (send_req_add_link_resp) smc_llc_send_req_add_link_response(req_qentry); @@ -1552,7 +1575,8 @@ void smc_llc_add_link_local(struct smc_link *link) add_llc.hd.common.llc_type = SMC_LLC_ADD_LINK; smc_llc_init_msg_hdr(&add_llc.hd, link->lgr, sizeof(add_llc)); /* no dev and port needed */ - smc_llc_enqueue(link, (union smc_llc_msg *)&add_llc); + smc_llc_enqueue(link, (union smc_llc_msg *)&add_llc, + sizeof(union smc_llc_msg)); } /* worker to process an add link message */ @@ -1588,7 +1612,8 @@ void smc_llc_srv_delete_link_local(struct smc_link *link, u8 del_link_id) del_llc.link_num = del_link_id; del_llc.reason = htonl(SMC_LLC_DEL_LOST_PATH); del_llc.hd.flags |= SMC_LLC_FLAG_DEL_LINK_ORDERLY; - smc_llc_enqueue(link, (union smc_llc_msg *)&del_llc); + smc_llc_enqueue(link, (union smc_llc_msg *)&del_llc, + sizeof(union smc_llc_msg)); } static void smc_llc_process_cli_delete_link(struct smc_link_group *lgr) @@ -1810,19 +1835,28 @@ static void smc_llc_rmt_delete_rkey(struct smc_link_group *lgr) link = qentry->link; if (lgr->smc_version == SMC_V2) { - struct smc_llc_msg_delete_rkey_v2 *llcv2; + struct smc_llc_msg_delete_rkey_v2_inline *llcv2; - if (smc_link_shared_v2_rxbuf(link)) { - memcpy(lgr->wr_rx_buf_v2, llc, sizeof(*llc)); - llcv2 = (struct smc_llc_msg_delete_rkey_v2 *)lgr->wr_rx_buf_v2; - } else { - llcv2 = (struct smc_llc_msg_delete_rkey_v2 *)llc; - } + /* The leading SMC_LLC_DEL_RKEY_V2_INLINE rkeys are declared in + * the message itself, any further ones were received into + * qentry->body. + */ + llcv2 = &qentry->msg.delete_rkey_v2; llcv2->num_inval_rkeys = 0; max = min_t(u8, llcv2->num_rkeys, SMC_LLC_RKEYS_PER_MSG_V2); + max = min_t(u32, max, SMC_LLC_DEL_RKEY_V2_INLINE + + qentry->body_len / sizeof(__be32)); for (i = 0; i < max; i++) { - if (smc_rtoken_delete(link, llcv2->rkey[i])) + __be32 rkey; + + if (i < SMC_LLC_DEL_RKEY_V2_INLINE) + rkey = llcv2->rkey[i]; + else + memcpy(&rkey, qentry->body + + (i - SMC_LLC_DEL_RKEY_V2_INLINE) * + sizeof(rkey), sizeof(rkey)); + if (smc_rtoken_delete(link, rkey)) llcv2->num_inval_rkeys++; } memset(&llc->rkey[0], 0, sizeof(llc->rkey)); @@ -2064,18 +2098,52 @@ assign: wake_up(&link->lgr->llc_msg_waiter); } -static void smc_llc_enqueue(struct smc_link *link, union smc_llc_msg *llc) +/* the longest tail either reader of qentry->body can use */ +static u32 smc_llc_max_body_len(union smc_llc_msg *llc) +{ + switch (llc->raw.hdr.common.llc_type) { + case SMC_LLC_ADD_LINK: + return offsetof(struct smc_llc_msg_add_link_v2_ext, rt) + + SMC_LLC_RKEYS_PER_MSG_V2 * + sizeof(struct smc_llc_msg_add_link_cont_rt); + case SMC_LLC_DELETE_RKEY: + return (SMC_LLC_RKEYS_PER_MSG_V2 - + SMC_LLC_DEL_RKEY_V2_INLINE) * sizeof(__be32); + default: + return 0; + } +} + +static void smc_llc_enqueue(struct smc_link *link, union smc_llc_msg *llc, + u32 byte_len) { struct smc_link_group *lgr = link->lgr; struct smc_llc_qentry *qentry; unsigned long flags; + u16 body_len = 0; + + /* V2 messages can be longer than the inline union smc_llc_msg. Carry + * the remainder in the qentry itself, so that its lifetime and its + * length match the message the peer actually sent. + */ + if (lgr->smc_version == SMC_V2 && byte_len > SMC_WR_TX_SIZE) + body_len = min_t(u32, byte_len, SMC_WR_BUF_V2_SIZE) - + SMC_WR_TX_SIZE; + body_len = min_t(u32, body_len, smc_llc_max_body_len(llc)); - qentry = kmalloc_obj(*qentry, GFP_ATOMIC); + qentry = kmalloc_flex(*qentry, body, body_len, GFP_ATOMIC); if (!qentry) return; + qentry->body_len = body_len; qentry->link = link; INIT_LIST_HEAD(&qentry->list); memcpy(&qentry->msg, llc, sizeof(union smc_llc_msg)); + if (body_len) { + u8 *src = smc_link_shared_v2_rxbuf(link) ? + (u8 *)lgr->wr_rx_buf_v2 : (u8 *)llc; + + memcpy(qentry->body, src + SMC_WR_TX_SIZE, body_len); + } /* process responses immediately */ if ((llc->raw.hdr.flags & SMC_LLC_FLAG_RESP) && @@ -2107,7 +2175,7 @@ static void smc_llc_rx_handler(struct ib_wc *wc, void *buf) return; /* invalid message */ } - smc_llc_enqueue(link, llc); + smc_llc_enqueue(link, llc, wc->byte_len); } /***************************** worker, utils *********************************/ diff --git a/net/smc/smc_rx.c b/net/smc/smc_rx.c index 5c9e4d8b57de..197fddc6271f 100644 --- a/net/smc/smc_rx.c +++ b/net/smc/smc_rx.c @@ -115,16 +115,15 @@ static void smc_rx_pipe_buf_release(struct pipe_inode_info *pipe, struct pipe_buffer *buf) { struct smc_spd_priv *priv = (struct smc_spd_priv *)buf->private; + struct smc_connection *conn = &priv->smc->conn; struct smc_sock *smc = priv->smc; - struct smc_connection *conn; struct sock *sk = &smc->sk; - if (sk->sk_state == SMC_CLOSED || - sk->sk_state == SMC_PEERFINCLOSEWAIT || - sk->sk_state == SMC_APPFINCLOSEWAIT) - goto out; - conn = &smc->conn; lock_sock(sk); + if (conn->freed) { + release_sock(sk); + goto out; + } smc_rx_update_cons(smc, priv->len); release_sock(sk); if (atomic_sub_and_test(priv->len, &conn->splice_pending)) diff --git a/net/smc/smc_tx.h b/net/smc/smc_tx.h index a59f370b8b43..610a945aefd6 100644 --- a/net/smc/smc_tx.h +++ b/net/smc/smc_tx.h @@ -20,11 +20,15 @@ static inline int smc_tx_prepared_sends(struct smc_connection *conn) { + struct smc_buf_desc *sndbuf_desc = READ_ONCE(conn->sndbuf_desc); union smc_host_cursor sent, prep; + if (!sndbuf_desc) + return 0; + smc_curs_copy(&sent, &conn->tx_curs_sent, conn); smc_curs_copy(&prep, &conn->tx_curs_prep, conn); - return smc_curs_diff(conn->sndbuf_desc->len, &sent, &prep); + return smc_curs_diff(sndbuf_desc->len, &sent, &prep); } void smc_tx_pending(struct smc_connection *conn); diff --git a/net/smc/smc_wr.c b/net/smc/smc_wr.c index 59c92b46945c..97ba46893b17 100644 --- a/net/smc/smc_wr.c +++ b/net/smc/smc_wr.c @@ -602,9 +602,9 @@ static void smc_wr_init_sge(struct smc_link *lnk) /* With SMC-Rv2 there can be messages larger than SMC_WR_TX_SIZE. * Each ib_recv_wr gets 2 sges, the second one is a spillover buffer - * and the same buffer for all sges. When a larger message arrived then - * the content of the first small sge is copied to the beginning of - * the larger spillover buffer, allowing easy data mapping. + * and the same buffer for all sges. The spillover sge starts at + * SMC_WR_TX_SIZE, so the leading bytes of that buffer are never + * written. */ for (i = 0; i < lnk->wr_rx_cnt; i++) { int x = i * lnk->wr_rx_sge_cnt; diff --git a/net/sunrpc/auth_gss/auth_gss.c b/net/sunrpc/auth_gss/auth_gss.c index 9d3fb6848f40..8ddc65e894da 100644 --- a/net/sunrpc/auth_gss/auth_gss.c +++ b/net/sunrpc/auth_gss/auth_gss.c @@ -2072,7 +2072,11 @@ gss_unwrap_resp_priv(struct rpc_task *task, struct rpc_cred *cred, goto unwrap_failed; opaque_len = be32_to_cpup(p++); offset = (u8 *)(p) - (u8 *)head->iov_base; - if (offset + opaque_len > rcv_buf->len) + if (offset > rcv_buf->len) + goto unwrap_failed; + if (opaque_len > rcv_buf->len - offset) + goto unwrap_failed; + if (opaque_len <= GSS_KRB5_TOK_HDR_LEN) goto unwrap_failed; maj_stat = gss_unwrap(ctx->gc_gss_ctx, offset, diff --git a/net/sunrpc/auth_gss/gss_krb5_unseal.c b/net/sunrpc/auth_gss/gss_krb5_unseal.c index b5fb70419faa..4d12d49434c2 100644 --- a/net/sunrpc/auth_gss/gss_krb5_unseal.c +++ b/net/sunrpc/auth_gss/gss_krb5_unseal.c @@ -89,6 +89,9 @@ gss_krb5_verify_mic_v2(struct krb5_ctx *ctx, struct xdr_buf *message_buffer, dprintk("RPC: %s\n", __func__); + if (read_token->len < GSS_KRB5_TOK_HDR_LEN + cksum_len) + return GSS_S_DEFECTIVE_TOKEN; + memcpy(&be16_ptr, (char *) ptr, 2); if (be16_to_cpu(be16_ptr) != KG2_TOK_MIC) return GSS_S_DEFECTIVE_TOKEN; diff --git a/net/sunrpc/auth_gss/gss_krb5_wrap.c b/net/sunrpc/auth_gss/gss_krb5_wrap.c index ac4b32df42b9..d3f61c4b5a13 100644 --- a/net/sunrpc/auth_gss/gss_krb5_wrap.c +++ b/net/sunrpc/auth_gss/gss_krb5_wrap.c @@ -73,6 +73,8 @@ static void _rotate_left(struct xdr_buf *buf, unsigned int shift) int shifted = 0; int this_shift; + if (!buf->len) + return; shift %= buf->len; while (shifted < shift) { this_shift = min(shift - shifted, LOCAL_BUF_LEN); @@ -85,6 +87,8 @@ static void rotate_left(u32 base, struct xdr_buf *buf, unsigned int shift) { struct xdr_buf subbuf; + if (buf->len <= base) + return; xdr_buf_subsegment(buf, &subbuf, base, buf->len - base); _rotate_left(&subbuf, shift); } @@ -154,6 +158,9 @@ gss_krb5_unwrap_v2(struct krb5_ctx *kctx, int offset, int len, dprintk("RPC: %s\n", __func__); + if (len - offset <= GSS_KRB5_TOK_HDR_LEN) + return GSS_S_DEFECTIVE_TOKEN; + ptr = buf->head[0].iov_base + offset; if (be16_to_cpu(*((__be16 *)ptr)) != KG2_TOK_WRAP) @@ -220,14 +227,16 @@ gss_krb5_unwrap_v2(struct krb5_ctx *kctx, int offset, int len, * head buffer space rather than that actually occupied. */ movelen = min_t(unsigned int, buf->head[0].iov_len, len); + if (movelen < offset + GSS_KRB5_TOK_HDR_LEN + headskip) + return GSS_S_DEFECTIVE_TOKEN; movelen -= offset + GSS_KRB5_TOK_HDR_LEN + headskip; - BUG_ON(offset + GSS_KRB5_TOK_HDR_LEN + headskip + movelen > - buf->head[0].iov_len); memmove(ptr, ptr + GSS_KRB5_TOK_HDR_LEN + headskip, movelen); buf->head[0].iov_len -= GSS_KRB5_TOK_HDR_LEN + headskip; buf->len = len - (GSS_KRB5_TOK_HDR_LEN + headskip); /* Trim off the trailing "extra count" and checksum blob */ + if (ec + GSS_KRB5_TOK_HDR_LEN + tailskip > buf->len - offset) + return GSS_S_DEFECTIVE_TOKEN; xdr_buf_trim(buf, ec + GSS_KRB5_TOK_HDR_LEN + tailskip); *align = XDR_QUADLEN(GSS_KRB5_TOK_HDR_LEN + headskip); diff --git a/net/sunrpc/auth_gss/gss_rpc_upcall.c b/net/sunrpc/auth_gss/gss_rpc_upcall.c index 0fa4778620d9..b7f70b1adb18 100644 --- a/net/sunrpc/auth_gss/gss_rpc_upcall.c +++ b/net/sunrpc/auth_gss/gss_rpc_upcall.c @@ -121,12 +121,6 @@ out: return result; } -void init_gssp_clnt(struct sunrpc_net *sn) -{ - mutex_init(&sn->gssp_lock); - sn->gssp_clnt = NULL; -} - int set_gssp_clnt(struct net *net) { struct sunrpc_net *sn = net_generic(net, sunrpc_net_id); diff --git a/net/sunrpc/auth_gss/gss_rpc_upcall.h b/net/sunrpc/auth_gss/gss_rpc_upcall.h index 31e96344167e..b3c2b2b90798 100644 --- a/net/sunrpc/auth_gss/gss_rpc_upcall.h +++ b/net/sunrpc/auth_gss/gss_rpc_upcall.h @@ -29,7 +29,6 @@ int gssp_accept_sec_context_upcall(struct net *net, struct gssp_upcall_data *data); void gssp_free_upcall_data(struct gssp_upcall_data *data); -void init_gssp_clnt(struct sunrpc_net *); int set_gssp_clnt(struct net *); void clear_gssp_clnt(struct sunrpc_net *); diff --git a/net/sunrpc/auth_gss/gss_rpc_xdr.c b/net/sunrpc/auth_gss/gss_rpc_xdr.c index fceee648d545..0549edae1ebe 100644 --- a/net/sunrpc/auth_gss/gss_rpc_xdr.c +++ b/net/sunrpc/auth_gss/gss_rpc_xdr.c @@ -222,7 +222,8 @@ static int gssx_dec_linux_creds(struct xdr_stream *xdr, return 0; out_free_groups: - groups_free(creds->cr_group_info); + put_group_info(creds->cr_group_info); + creds->cr_group_info = NULL; return err; } @@ -230,6 +231,7 @@ static int gssx_dec_option_array(struct xdr_stream *xdr, struct gssx_option_array *oa) { struct svc_cred *creds; + bool creds_decoded = false; u32 count, i; __be32 *p; int err; @@ -242,12 +244,12 @@ static int gssx_dec_option_array(struct xdr_stream *xdr, return 0; /* we recognize only 1 currently: CREDS_VALUE */ - oa->count = 1; - oa->data = kmalloc_obj(struct gssx_option); if (!oa->data) return -ENOMEM; + oa->count = 1; + creds = kzalloc_obj(struct svc_cred); if (!creds) { err = -ENOMEM; @@ -280,9 +282,14 @@ static int gssx_dec_option_array(struct xdr_stream *xdr, if (length == sizeof(CREDS_VALUE) && memcmp(p, CREDS_VALUE, sizeof(CREDS_VALUE)) == 0) { /* We have creds here. parse them */ + if (creds_decoded) { + err = -EINVAL; + goto free_creds; + } err = gssx_dec_linux_creds(xdr, creds); if (err) goto free_creds; + creds_decoded = true; oa->data[0].value.len = 1; /* presence */ } else { /* consume uninteresting buffer */ @@ -294,8 +301,10 @@ static int gssx_dec_option_array(struct xdr_stream *xdr, return 0; free_creds: + free_svc_cred(creds); kfree(creds); free_oa: + oa->count = 0; kfree(oa->data); oa->data = NULL; return err; diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c index d14209031e18..967e9d53080d 100644 --- a/net/sunrpc/auth_gss/svcauth_gss.c +++ b/net/sunrpc/auth_gss/svcauth_gss.c @@ -949,6 +949,8 @@ svcauth_gss_unwrap_priv(struct svc_rqst *rqstp, u32 seq, struct gss_ctx *ctx) } if (len > xdr_stream_remaining(xdr)) goto unwrap_failed; + if (len <= GSS_KRB5_TOK_HDR_LEN) + goto unwrap_failed; offset = xdr_stream_pos(xdr); saved_len = buf->len; @@ -1466,7 +1468,6 @@ static int create_use_gss_proxy_proc_entry(struct net *net) &use_gss_proxy_proc_ops, net); if (!*p) return -ENOMEM; - init_gssp_clnt(sn); return 0; } @@ -1573,6 +1574,9 @@ svcauth_gss_decode_credbody(struct xdr_stream *xdr, u32 body_len; __be32 *p; + /* Early-return paths leave deterministic state, not stale residue. */ + memset(gc, 0, sizeof(*gc)); + p = xdr_inline_decode(xdr, XDR_UNIT); if (!p) return false; @@ -1942,6 +1946,8 @@ svcauth_gss_release(struct svc_rqst *rqstp) if (!gsd) goto out; + if (rqstp->rq_auth_stat != rpc_auth_ok) + goto out; gc = &gsd->clcred; if (gc->gc_proc != RPC_GSS_PROC_DATA) goto out; diff --git a/net/sunrpc/backchannel_rqst.c b/net/sunrpc/backchannel_rqst.c index 0ffa4d01a938..1482b06e0f38 100644 --- a/net/sunrpc/backchannel_rqst.c +++ b/net/sunrpc/backchannel_rqst.c @@ -25,20 +25,39 @@ unsigned int xprt_bc_max_slots(struct rpc_xprt *xprt) } /* - * Helper function to nullify backchannel server pointer in transport. - * We need to synchronize setting the pointer to NULL (done so after - * the backchannel server is shutdown) with the usage of that pointer - * by the backchannel request processing routines - * xprt_complete_bc_request() and rpcrdma_bc_receive_call(). + * Close the backchannel producer side, drain any requests still + * queued on sv_cb_list, then destroy the callback service. */ void xprt_svc_destroy_nullify_bc(struct rpc_xprt *xprt, struct svc_serv **serv) { - spin_lock(&xprt->bc_pa_lock); + struct svc_serv *bc_serv = *serv; + struct rpc_rqst *req; + + xprt_svc_shutdown_bc(xprt); + while ((req = lwq_dequeue(&bc_serv->sv_cb_list, struct rpc_rqst, + rq_bc_list)) != NULL) { + atomic_dec(&req->rq_xprt->bc_slot_count); + xprt_free_bc_request(req); + } svc_destroy(serv); +} +EXPORT_SYMBOL_GPL(xprt_svc_destroy_nullify_bc); + +/* + * Clear the backchannel server pointer in the transport. The NULL + * store is serialized under bc_pa_lock against readers of + * xprt->bc_serv in xprt_complete_bc_request() and + * rpcrdma_bc_receive_call(). Clearing it before the callback service + * is stopped prevents a producer from enqueueing onto a service that + * is being torn down. + */ +void xprt_svc_shutdown_bc(struct rpc_xprt *xprt) +{ + spin_lock(&xprt->bc_pa_lock); xprt->bc_serv = NULL; spin_unlock(&xprt->bc_pa_lock); } -EXPORT_SYMBOL_GPL(xprt_svc_destroy_nullify_bc); +EXPORT_SYMBOL_GPL(xprt_svc_shutdown_bc); /* * Helper routines that track the number of preallocation elements @@ -393,7 +412,12 @@ void xprt_enqueue_bc_request(struct rpc_rqst *req) if (bc_serv) { lwq_enqueue(&req->rq_bc_list, &bc_serv->sv_cb_list); svc_pool_wake_idle_thread(&bc_serv->sv_pools[0]); + spin_unlock(&xprt->bc_pa_lock); + return; } spin_unlock(&xprt->bc_pa_lock); + + atomic_dec(&xprt->bc_slot_count); + xprt_free_bc_request(req); } EXPORT_SYMBOL_GPL(xprt_enqueue_bc_request); diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c index 391037f15292..1bc04109d213 100644 --- a/net/sunrpc/cache.c +++ b/net/sunrpc/cache.c @@ -430,10 +430,9 @@ void sunrpc_destroy_cache_detail(struct cache_detail *cd) list_del_init(&cd->others); spin_unlock(&cd->hash_lock); spin_unlock(&cache_list_lock); - if (list_empty(&cache_list)) { - /* module must be being unloaded so its safe to kill the worker */ - cancel_delayed_work_sync(&cache_cleaner); - } + cancel_delayed_work_sync(&cache_cleaner); + if (!list_empty(&cache_list)) + queue_delayed_work(system_power_efficient_wq, &cache_cleaner, 0); } EXPORT_SYMBOL_GPL(sunrpc_destroy_cache_detail); diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c index efa26899bc7d..6cedc824cf82 100644 --- a/net/sunrpc/clnt.c +++ b/net/sunrpc/clnt.c @@ -96,7 +96,10 @@ static void rpc_unregister_client(struct rpc_clnt *clnt) static void __rpc_clnt_remove_pipedir(struct rpc_clnt *clnt) { - rpc_remove_client_dir(clnt); + if (clnt->pipefs_sb) { + rpc_remove_client_dir(clnt); + clnt->pipefs_sb = NULL; + } } static void rpc_clnt_remove_pipedir(struct rpc_clnt *clnt) @@ -177,19 +180,28 @@ static int rpc_clnt_skip_event(struct rpc_clnt *clnt, unsigned long event) } static int __rpc_clnt_handle_event(struct rpc_clnt *clnt, unsigned long event, - struct super_block *sb) + struct super_block *sb) { + int err = 0; + switch (event) { case RPC_PIPEFS_MOUNT: - return rpc_setup_pipedir_sb(sb, clnt); + clnt->pipefs_sb = sb; + err = rpc_setup_pipedir_sb(sb, clnt); + if (err) + clnt->pipefs_sb = NULL; + break; case RPC_PIPEFS_UMOUNT: - __rpc_clnt_remove_pipedir(clnt); + if (clnt->pipefs_sb == sb) { + __rpc_clnt_remove_pipedir(clnt); + clnt->pipefs_sb = NULL; + } break; default: printk(KERN_ERR "%s: unknown event: %ld\n", __func__, event); return -ENOTSUPP; } - return 0; + return err; } static int __rpc_pipefs_event(struct rpc_clnt *clnt, unsigned long event, diff --git a/net/sunrpc/sunrpc_syms.c b/net/sunrpc/sunrpc_syms.c index ab88ce46afb5..1a3884a0376a 100644 --- a/net/sunrpc/sunrpc_syms.c +++ b/net/sunrpc/sunrpc_syms.c @@ -57,6 +57,7 @@ static __net_init int sunrpc_init_net(struct net *net) INIT_LIST_HEAD(&sn->all_clients); spin_lock_init(&sn->rpc_client_lock); spin_lock_init(&sn->rpcb_clnt_lock); + mutex_init(&sn->gssp_lock); return 0; err_pipefs: diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c index ae9ec4bf34f7..f85914ccb01a 100644 --- a/net/sunrpc/svc.c +++ b/net/sunrpc/svc.c @@ -352,7 +352,7 @@ static int svc_pool_map_get_node(unsigned int pidx) if (m->mode == SVC_POOL_PERNODE) return m->pool_to[pidx]; } - return numa_mem_id(); + return NUMA_NO_NODE; } /* * Set the given thread's cpus_allowed mask so that it @@ -402,6 +402,7 @@ struct svc_pool *svc_pool_for_cpu(struct svc_serv *serv) struct svc_pool_map *m = &svc_pool_map; int cpu = raw_smp_processor_id(); unsigned int pidx = 0; + unsigned int i; if (serv->sv_nrpools <= 1) return serv->sv_pools; @@ -414,8 +415,34 @@ struct svc_pool *svc_pool_for_cpu(struct svc_serv *serv) pidx = m->to_pool[cpu_to_node(cpu)]; break; } + pidx %= serv->sv_nrpools; + + /* + * It's possible to have a pool with no threads. Userland can just set + * things up this way directly. Also, when threads are autodistributed + * they are spread evenly across the pools, but when there are fewer + * threads than pools some pools can end up with none. + * + * A transport enqueued on a threadless pool would never be picked up, + * since each thread only services its own pool. Fall back to the next + * populated pool, trading NUMA locality for a guarantee that the + * transport is serviced. + */ + for (i = 0; i < serv->sv_nrpools; i++) { + struct svc_pool *pool = &serv->sv_pools[pidx]; + + /* This is set under the service mutex and rarely ever + * changes. A data race here is harmless. + */ + if (data_race(pool->sp_nrthreads)) + return pool; + + if (++pidx >= serv->sv_nrpools) + pidx = 0; + } - return &serv->sv_pools[pidx % serv->sv_nrpools]; + /* No pool has any threads; nothing can service the transport. */ + return &serv->sv_pools[pidx]; } static int svc_rpcb_setup(struct svc_serv *serv, struct net *net) @@ -476,6 +503,35 @@ __svc_init_bc(struct svc_serv *serv) } #endif +static int svc_pool_init_counters(struct svc_pool *pool) +{ + int err; + + err = percpu_counter_init(&pool->sp_messages_arrived, 0, GFP_KERNEL); + if (err) + return err; + err = percpu_counter_init(&pool->sp_sockets_queued, 0, GFP_KERNEL); + if (err) + goto err_sockets; + err = percpu_counter_init(&pool->sp_threads_woken, 0, GFP_KERNEL); + if (err) + goto err_threads; + return 0; + +err_threads: + percpu_counter_destroy(&pool->sp_sockets_queued); +err_sockets: + percpu_counter_destroy(&pool->sp_messages_arrived); + return err; +} + +static void svc_pool_destroy_counters(struct svc_pool *pool) +{ + percpu_counter_destroy(&pool->sp_messages_arrived); + percpu_counter_destroy(&pool->sp_sockets_queued); + percpu_counter_destroy(&pool->sp_threads_woken); +} + /* * Create an RPC service */ @@ -540,12 +596,18 @@ __svc_create(struct svc_program *prog, int nprogs, struct svc_stat *stats, INIT_LIST_HEAD(&pool->sp_all_threads); init_llist_head(&pool->sp_idle_threads); - percpu_counter_init(&pool->sp_messages_arrived, 0, GFP_KERNEL); - percpu_counter_init(&pool->sp_sockets_queued, 0, GFP_KERNEL); - percpu_counter_init(&pool->sp_threads_woken, 0, GFP_KERNEL); + if (svc_pool_init_counters(pool)) + goto out_err; } return serv; + +out_err: + while (i--) + svc_pool_destroy_counters(&serv->sv_pools[i]); + kfree(serv->sv_pools); + kfree(serv); + return NULL; } /** @@ -624,9 +686,7 @@ svc_destroy(struct svc_serv **servp) for (i = 0; i < serv->sv_nrpools; i++) { struct svc_pool *pool = &serv->sv_pools[i]; - percpu_counter_destroy(&pool->sp_messages_arrived); - percpu_counter_destroy(&pool->sp_sockets_queued); - percpu_counter_destroy(&pool->sp_threads_woken); + svc_pool_destroy_counters(pool); } kfree(serv->sv_pools); kfree(serv); @@ -683,6 +743,15 @@ svc_release_buffer(struct svc_rqst *rqstp) } } +static void svc_rqst_free_rcu(struct rcu_head *head) +{ + struct svc_rqst *rqstp = container_of(head, struct svc_rqst, rq_rcu_head); + + kfree(rqstp->rq_resp); + kfree(rqstp->rq_argp); + kfree(rqstp); +} + static void svc_rqst_free(struct svc_rqst *rqstp) { @@ -691,10 +760,8 @@ svc_rqst_free(struct svc_rqst *rqstp) svc_release_buffer(rqstp); if (rqstp->rq_scratch_folio) folio_put(rqstp->rq_scratch_folio); - kfree(rqstp->rq_resp); - kfree(rqstp->rq_argp); kfree(rqstp->rq_auth_data); - kfree_rcu(rqstp, rq_rcu_head); + call_rcu(&rqstp->rq_rcu_head, svc_rqst_free_rcu); } static struct svc_rqst * @@ -711,7 +778,9 @@ svc_prepare_thread(struct svc_serv *serv, struct svc_pool *pool, int node) rqstp->rq_server = serv; rqstp->rq_pool = pool; - rqstp->rq_scratch_folio = __folio_alloc_node(GFP_KERNEL, 0, node); + rqstp->rq_scratch_folio = __folio_alloc_node(GFP_KERNEL, 0, + node == NUMA_NO_NODE ? + numa_mem_id() : node); if (!rqstp->rq_scratch_folio) goto out_enomem; diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c index fa6a30b5f046..cb2ef428651f 100644 --- a/net/sunrpc/xdr.c +++ b/net/sunrpc/xdr.c @@ -2049,7 +2049,7 @@ void xdr_buf_trim(struct xdr_buf *buf, unsigned int len) trim -= cur; } fix_len: - buf->len -= (len - trim); + buf->len -= min_t(unsigned int, buf->len, len - trim); } EXPORT_SYMBOL_GPL(xdr_buf_trim); diff --git a/net/sunrpc/xprtrdma/ib_client.c b/net/sunrpc/xprtrdma/ib_client.c index de49ad02053d..69166d5d9987 100644 --- a/net/sunrpc/xprtrdma/ib_client.c +++ b/net/sunrpc/xprtrdma/ib_client.c @@ -51,7 +51,11 @@ static struct rpcrdma_device *rpcrdma_get_client_data(struct ib_device *device) * to be invoked when the device is removed, unless this notification * is unregistered first. * - * On failure, a negative errno is returned. + * On failure, a negative errno is returned. rn->rn_done is left + * NULL on every failure path (it is assigned only after xa_alloc + * and kref_get have both succeeded), so the @rn may safely be + * passed to rpcrdma_rn_unregister() without a separate + * registered/unregistered flag in the caller. */ int rpcrdma_rn_register(struct ib_device *device, struct rpcrdma_notification *rn, @@ -83,6 +87,10 @@ static void rpcrdma_rn_release(struct kref *kref) * rpcrdma_rn_unregister - stop device removal notifications * @device: monitored device * @rn: notification object that no longer wishes to be notified + * + * It is safe to call this on an @rn whose registration never + * completed or failed; rn_done == NULL is treated as + * never-registered and the call is a no-op. */ void rpcrdma_rn_unregister(struct ib_device *device, struct rpcrdma_notification *rn) @@ -92,6 +100,20 @@ void rpcrdma_rn_unregister(struct ib_device *device, if (!rd) return; + /* + * rn_done is the registration sentinel: rpcrdma_rn_register + * assigns it last, after xa_alloc and kref_get have both + * succeeded. A NULL rn_done means this notification was + * never registered (or its registration failed) or has + * already been unregistered, and the call is a no-op. + * Without this guard, rn_index == 0 from a kzalloc'd + * parent would erase another caller's slot 0 and underflow + * rd_kref. + */ + if (!rn->rn_done) + return; + rn->rn_done = NULL; + trace_rpcrdma_client_unregister(device, rn); xa_erase(&rd->rd_xa, rn->rn_index); kref_put(&rd->rd_kref, rpcrdma_rn_release); diff --git a/net/sunrpc/xprtrdma/svc_rdma_pcl.c b/net/sunrpc/xprtrdma/svc_rdma_pcl.c index 1f8f7dad8b6f..18d1045799ce 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_pcl.c +++ b/net/sunrpc/xprtrdma/svc_rdma_pcl.c @@ -213,7 +213,6 @@ bool pcl_alloc_write(struct svc_rdma_recv_ctxt *rctxt, chunk = pcl_alloc_chunk(segcount, 0); if (!chunk) return false; - list_add_tail(&chunk->ch_list, &pcl->cl_chunks); for (j = 0; j < segcount; j++) { segment = &chunk->ch_segments[j]; @@ -225,6 +224,7 @@ bool pcl_alloc_write(struct svc_rdma_recv_ctxt *rctxt, chunk->ch_length += segment->rs_length; chunk->ch_segcount++; } + list_add_tail(&chunk->ch_list, &pcl->cl_chunks); } return true; } diff --git a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c index fe9bf0371b6e..8e71ceec11df 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c +++ b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c @@ -440,11 +440,14 @@ static void svc_rdma_build_arg_xdr(struct svc_rqst *rqstp, * to the first byte past the Read list. rc_read_pcl and * rc_call_pcl cl_count fields are set to the number of * Read segments in the list. - * %false: Read list is corrupt. @rctxt's xdr_stream is left in an - * unknown state. + * %false: Read list is corrupt or exceeds the page budget. @rctxt's + * xdr_stream is left in an unknown state. */ static bool xdr_count_read_segments(struct svc_rdma_recv_ctxt *rctxt, __be32 *p) { + unsigned int maxlen = rctxt->rc_maxpages << PAGE_SHIFT; + unsigned int total_len = 0; + rctxt->rc_call_pcl.cl_count = 0; rctxt->rc_read_pcl.cl_count = 0; while (xdr_item_is_present(p)) { @@ -458,6 +461,11 @@ static bool xdr_count_read_segments(struct svc_rdma_recv_ctxt *rctxt, __be32 *p) xdr_decode_read_segment(p, &position, &handle, &length, &offset); + if (length > maxlen) + return false; + total_len += length; + if (PAGE_ALIGN(total_len) > maxlen) + return false; if (position) { if (position & 3) return false; @@ -508,10 +516,13 @@ static bool xdr_check_write_chunk(struct svc_rdma_recv_ctxt *rctxt) return false; /* Before trusting the segcount value enough to use it in - * a computation, perform a simple range check. This is an - * arbitrary but sensible limit (ie, not architectural). + * a computation, perform a simple range check. A zero + * segcount describes no remote buffer and is rejected so + * downstream consumers never see a degenerate ch_segcount==0 + * chunk. The upper bound is an arbitrary but sensible limit + * (ie, not architectural). */ - if (unlikely(segcount > rctxt->rc_maxpages)) + if (segcount == 0 || unlikely(segcount > rctxt->rc_maxpages)) return false; p = xdr_inline_decode(&rctxt->rc_stream, diff --git a/net/sunrpc/xprtrdma/svc_rdma_rw.c b/net/sunrpc/xprtrdma/svc_rdma_rw.c index f7fd22cc4a59..9aaaade99e6e 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_rw.c +++ b/net/sunrpc/xprtrdma/svc_rdma_rw.c @@ -795,7 +795,7 @@ static int svc_rdma_build_read_segment(struct svc_rqst *rqstp, len -= seg_len; if (len && ((head->rc_curpage + 1) > rqstp->rq_maxpages)) - goto out_overrun; + goto out_put; } ret = svc_rdma_rw_ctx_init(rdma, ctxt, segment->rs_offset, @@ -809,7 +809,8 @@ static int svc_rdma_build_read_segment(struct svc_rqst *rqstp, cc->cc_sqecount += ret; return 0; -out_overrun: +out_put: + svc_rdma_put_rw_ctxt(rdma, ctxt); trace_svcrdma_page_overrun_err(&cc->cc_cid, head->rc_curpage); return -EINVAL; } @@ -847,7 +848,7 @@ static int svc_rdma_build_read_chunk(struct svc_rqst *rqstp, * svc_rdma_copy_inline_range - Copy part of the inline content into pages * @rqstp: RPC transaction context * @head: context for ongoing I/O - * @offset: offset into the Receive buffer of region to copy + * @offset: offset into the inline content of region to copy * @remaining: length of region to copy * * Take a page at a time from rqstp->rq_pages and copy the inline @@ -864,9 +865,13 @@ static int svc_rdma_copy_inline_range(struct svc_rqst *rqstp, unsigned int offset, unsigned int remaining) { - unsigned char *dst, *src = head->rc_recv_buf; + unsigned char *dst, *src = head->rc_saved_arg.head[0].iov_base; + unsigned int inline_len = head->rc_saved_arg.head[0].iov_len; unsigned int page_no, numpages; + if (offset > inline_len || remaining > inline_len - offset) + return -EINVAL; + numpages = PAGE_ALIGN(head->rc_pageoff + remaining) >> PAGE_SHIFT; for (page_no = 0; page_no < numpages; page_no++) { unsigned int page_len; @@ -917,9 +922,10 @@ svc_rdma_read_multiple_chunks(struct svc_rqst *rqstp, { const struct svc_rdma_pcl *pcl = &head->rc_read_pcl; struct svc_rdma_chunk *chunk, *next; - unsigned int start, length; + unsigned int inline_len, start, length; int ret; + inline_len = head->rc_saved_arg.head[0].iov_len; start = 0; chunk = pcl_first_chunk(pcl); length = chunk->ch_position; @@ -937,6 +943,8 @@ svc_rdma_read_multiple_chunks(struct svc_rqst *rqstp, break; start += length; + if (head->rc_readbytes > next->ch_position) + return -EINVAL; length = next->ch_position - head->rc_readbytes; ret = svc_rdma_copy_inline_range(rqstp, head, start, length); if (ret < 0) @@ -944,7 +952,9 @@ svc_rdma_read_multiple_chunks(struct svc_rqst *rqstp, } start += length; - length = head->rc_byte_len - start; + if (start > inline_len) + return -EINVAL; + length = inline_len - start; return svc_rdma_copy_inline_range(rqstp, head, start, length); } @@ -969,8 +979,12 @@ svc_rdma_read_multiple_chunks(struct svc_rqst *rqstp, static int svc_rdma_read_data_item(struct svc_rqst *rqstp, struct svc_rdma_recv_ctxt *head) { - return svc_rdma_build_read_chunk(rqstp, head, - pcl_first_chunk(&head->rc_read_pcl)); + struct svc_rdma_chunk *chunk = pcl_first_chunk(&head->rc_read_pcl); + + if (chunk->ch_position > head->rc_saved_arg.head[0].iov_len) + return -EINVAL; + + return svc_rdma_build_read_chunk(rqstp, head, chunk); } /** @@ -996,17 +1010,20 @@ static int svc_rdma_read_chunk_range(struct svc_rqst *rqstp, const struct svc_rdma_segment *segment; int ret; + if (!length) + return 0; + ret = -EINVAL; pcl_for_each_segment(segment, chunk) { struct svc_rdma_segment dummy; - if (offset > segment->rs_length) { + if (offset >= segment->rs_length) { offset -= segment->rs_length; continue; } dummy.rs_handle = segment->rs_handle; - dummy.rs_length = min_t(u32, length, segment->rs_length) - offset; + dummy.rs_length = min_t(u32, length, segment->rs_length - offset); dummy.rs_offset = segment->rs_offset + offset; ret = svc_rdma_build_read_segment(rqstp, head, &dummy); @@ -1015,6 +1032,8 @@ static int svc_rdma_read_chunk_range(struct svc_rqst *rqstp, head->rc_readbytes += dummy.rs_length; length -= dummy.rs_length; + if (!length) + break; offset = 0; } return ret; @@ -1039,14 +1058,17 @@ static int svc_rdma_read_call_chunk(struct svc_rqst *rqstp, pcl_first_chunk(&head->rc_call_pcl); const struct svc_rdma_pcl *pcl = &head->rc_read_pcl; struct svc_rdma_chunk *chunk, *next; - unsigned int start, length; + unsigned int call_len, start, length; int ret; if (pcl_is_empty(pcl)) return svc_rdma_build_read_chunk(rqstp, head, call_chunk); + call_len = call_chunk->ch_length; start = 0; chunk = pcl_first_chunk(pcl); + if (chunk->ch_position > call_len) + return -EINVAL; length = chunk->ch_position; ret = svc_rdma_read_chunk_range(rqstp, head, call_chunk, start, length); @@ -1063,6 +1085,10 @@ static int svc_rdma_read_call_chunk(struct svc_rqst *rqstp, break; start += length; + if (next->ch_position > call_len) + return -EINVAL; + if (head->rc_readbytes > next->ch_position) + return -EINVAL; length = next->ch_position - head->rc_readbytes; ret = svc_rdma_read_chunk_range(rqstp, head, call_chunk, start, length); @@ -1071,7 +1097,9 @@ static int svc_rdma_read_call_chunk(struct svc_rqst *rqstp, } start += length; - length = call_chunk->ch_length - start; + if (start > call_len) + return -EINVAL; + length = call_len - start; return svc_rdma_read_chunk_range(rqstp, head, call_chunk, start, length); } diff --git a/net/sunrpc/xprtrdma/svc_rdma_sendto.c b/net/sunrpc/xprtrdma/svc_rdma_sendto.c index 7f6d17bf8c1f..c09659b17351 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_sendto.c +++ b/net/sunrpc/xprtrdma/svc_rdma_sendto.c @@ -825,20 +825,21 @@ static int svc_rdma_xb_count_sges(const struct xdr_buf *xdr, } /** - * svc_rdma_pull_up_needed - Determine whether to use pull-up + * svc_rdma_check_pull_up - Determine whether to use pull-up * @rdma: controlling transport * @sctxt: send_ctxt for the Send WR * @write_pcl: Write chunk list provided by client * @xdr: xdr_buf containing RPC message to transmit * * Returns: - * %true if pull-up must be used - * %false otherwise + * %1 if pull-up must be used + * %0 if pull-up is not needed + * %-E2BIG if the reply is too large to be pulled up */ -static bool svc_rdma_pull_up_needed(const struct svcxprt_rdma *rdma, - const struct svc_rdma_send_ctxt *sctxt, - const struct svc_rdma_pcl *write_pcl, - const struct xdr_buf *xdr) +static int svc_rdma_check_pull_up(const struct svcxprt_rdma *rdma, + const struct svc_rdma_send_ctxt *sctxt, + const struct svc_rdma_pcl *write_pcl, + const struct xdr_buf *xdr) { /* Resources needed for the transport header */ struct svc_rdma_pullup_data args = { @@ -850,11 +851,22 @@ static bool svc_rdma_pull_up_needed(const struct svcxprt_rdma *rdma, ret = pcl_process_nonpayloads(write_pcl, xdr, svc_rdma_xb_count_sges, &args); if (ret < 0) - return false; + return 0; if (args.pd_length < RPCRDMA_PULLUP_THRESH) - return true; - return args.pd_num_sges >= rdma->sc_max_send_sges; + return 1; + if (args.pd_num_sges < rdma->sc_max_send_sges) + return 0; + + /* + * The reply has too many SGEs to Send inline, so it has to be + * linearized into sc_xprt_buf. That buffer holds only + * sc_max_req_size bytes, so a larger reply cannot be pulled up. + * RFC 8166 Section 4.5.3 requires responding with ERR_CHUNK. + */ + if (args.pd_length > rdma->sc_max_req_size) + return -E2BIG; + return 1; } /** @@ -910,7 +922,7 @@ static int svc_rdma_xb_linearize(const struct xdr_buf *xdr, * Assemble the elements of @xdr into the transport header buffer. * * Assumptions: - * pull_up_needed has determined that @xdr will fit in the buffer. + * check_pull_up has determined that @xdr will fit in the buffer. * * Returns: * %0 if pull-up was successful @@ -945,6 +957,7 @@ static int svc_rdma_pull_up_reply_msg(const struct svcxprt_rdma *rdma, * * Returns: * %0 if DMA mapping was successful. + * %-E2BIG if the reply is too large to be pulled up * %-EMSGSIZE if a buffer manipulation problem occurred * %-EIO if DMA mapping failed * @@ -960,6 +973,7 @@ int svc_rdma_map_reply_msg(struct svcxprt_rdma *rdma, .md_rdma = rdma, .md_ctxt = sctxt, }; + int ret; /* Set up the (persistently-mapped) transport header SGE. */ sctxt->sc_send_wr.num_sge = 1; @@ -974,7 +988,10 @@ int svc_rdma_map_reply_msg(struct svcxprt_rdma *rdma, /* For pull-up, svc_rdma_send() will sync the transport header. * No additional DMA mapping is necessary. */ - if (svc_rdma_pull_up_needed(rdma, sctxt, write_pcl, xdr)) + ret = svc_rdma_check_pull_up(rdma, sctxt, write_pcl, xdr); + if (ret < 0) + return ret; + if (ret) return svc_rdma_pull_up_reply_msg(rdma, sctxt, write_pcl, xdr); return pcl_process_nonpayloads(write_pcl, xdr, @@ -1162,7 +1179,7 @@ int svc_rdma_sendto(struct svc_rqst *rqstp) &rctxt->rc_reply_pcl, sctxt, &rqstp->rq_res); if (ret < 0) - goto reply_chunk; + goto send_err; rc_size = ret; } @@ -1183,10 +1200,10 @@ int svc_rdma_sendto(struct svc_rqst *rqstp) ret = svc_rdma_send_reply_msg(rdma, sctxt, rctxt, rqstp); if (ret < 0) - goto put_ctxt; + goto send_err; return 0; -reply_chunk: +send_err: if (ret != -E2BIG && ret != -EINVAL) goto put_ctxt; diff --git a/net/sunrpc/xprtrdma/svc_rdma_transport.c b/net/sunrpc/xprtrdma/svc_rdma_transport.c index 7ca71741106b..093371f9d245 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_transport.c +++ b/net/sunrpc/xprtrdma/svc_rdma_transport.c @@ -43,6 +43,7 @@ */ #include <linux/interrupt.h> +#include <linux/module.h> #include <linux/sched.h> #include <linux/slab.h> #include <linux/spinlock.h> @@ -245,12 +246,16 @@ svc_rdma_parse_connect_private(struct svcxprt_rdma *newxprt, * structure for the listening endpoint. * * This function creates a new xprt for the new connection and enqueues it on - * the accept queue for the listent xprt. When the listen thread is kicked, it + * the accept queue for the listen xprt. When the listen thread is kicked, it * will call the recvfrom method on the listen xprt which will accept the new * connection. + * + * Return values: + * %0: Do not destroy @new_cma_id + * %1: Destroy @new_cma_id (allocation failure) */ -static void handle_connect_req(struct rdma_cm_id *new_cma_id, - struct rdma_conn_param *param) +static int handle_connect_req(struct rdma_cm_id *new_cma_id, + struct rdma_conn_param *param) { struct svcxprt_rdma *listen_xprt = new_cma_id->context; struct svcxprt_rdma *newxprt; @@ -260,7 +265,7 @@ static void handle_connect_req(struct rdma_cm_id *new_cma_id, listen_xprt->sc_xprt.xpt_net, ibdev_to_node(new_cma_id->device)); if (!newxprt) - return; + return 1; newxprt->sc_cm_id = new_cma_id; new_cma_id->context = newxprt; svc_rdma_parse_connect_private(newxprt, param); @@ -294,6 +299,7 @@ static void handle_connect_req(struct rdma_cm_id *new_cma_id, set_bit(XPT_CONN, &listen_xprt->sc_xprt.xpt_flags); svc_xprt_enqueue(&listen_xprt->sc_xprt); + return 0; } /** @@ -317,14 +323,14 @@ static int svc_rdma_listen_handler(struct rdma_cm_id *cma_id, switch (event->event) { case RDMA_CM_EVENT_CONNECT_REQUEST: - handle_connect_req(cma_id, &event->param.conn); - break; + return handle_connect_req(cma_id, &event->param.conn); case RDMA_CM_EVENT_ADDR_CHANGE: listen_id = svc_rdma_create_listen_id(cma_rdma->xpt_net, sap, cma_xprt); if (IS_ERR(listen_id)) { pr_err("Listener dead, address change failed for device %s\n", cma_id->device->name); + cma_xprt->sc_cm_id = NULL; } else cma_xprt->sc_cm_id = listen_id; return 1; @@ -387,7 +393,13 @@ static struct svc_xprt *svc_rdma_create(struct svc_serv *serv, listen_id = svc_rdma_create_listen_id(net, sa, cma_xprt); if (IS_ERR(listen_id)) { - kfree(cma_xprt); + /* _svc_xprt_create() acquired one module reference and + * puts it on xpo_create failure. svc_xprt_free() puts + * a second one when the kref drops to zero. Take a + * compensating reference so both puts are balanced. + */ + __module_get(cma_xprt->sc_xprt.xpt_class->xcl_owner); + svc_xprt_put(&cma_xprt->sc_xprt); return ERR_CAST(listen_id); } cma_xprt->sc_cm_id = listen_id; @@ -598,13 +610,26 @@ static struct svc_xprt *svc_rdma_accept(struct svc_xprt *xprt) return &newxprt->sc_xprt; errout: - /* Take a reference in case the DTO handler runs */ - svc_xprt_get(&newxprt->sc_xprt); - if (newxprt->sc_qp && !IS_ERR(newxprt->sc_qp)) - ib_destroy_qp(newxprt->sc_qp); - rdma_destroy_id(newxprt->sc_cm_id); - rpcrdma_rn_unregister(dev, &newxprt->sc_rn); - /* This call to put will destroy the transport */ + /* + * Drop the kref_init birth reference. svc_xprt_free will + * dispatch xpo_free = svc_rdma_free, which tears down sc_qp, + * sc_sq_cq, sc_rq_cq, and sc_pd under existing IS_ERR/NULL + * guards, and sc_rn under the rn_done sentinel guard inside + * rpcrdma_rn_unregister. + * + * sc_cm_id is destroyed unconditionally by svc_rdma_free; that + * is safe here because sc_cm_id is non-NULL by caller invariant + * on every path that reaches this errout: handle_connect_req + * installs newxprt->sc_cm_id before queueing the new xprt for + * accept, and svc_rdma_accept has already dereferenced it above + * the first goto errout. + * + * svc_handle_xprt() drops its pre-acquired module reference when + * ->xpo_accept() returns NULL. Take a replacement reference before + * freeing @newxprt, because svc_xprt_free() drops the module + * reference associated with @newxprt. + */ + __module_get(newxprt->sc_xprt.xpt_class->xcl_owner); svc_xprt_put(&newxprt->sc_xprt); return NULL; } @@ -614,7 +639,8 @@ static void svc_rdma_detach(struct svc_xprt *xprt) struct svcxprt_rdma *rdma = container_of(xprt, struct svcxprt_rdma, sc_xprt); - rdma_disconnect(rdma->sc_cm_id); + if (rdma->sc_cm_id) + rdma_disconnect(rdma->sc_cm_id); /* * Most close paths go through svc_rdma_xprt_deferred_close(), @@ -634,10 +660,15 @@ static void svc_rdma_free(struct svc_xprt *xprt) { struct svcxprt_rdma *rdma = container_of(xprt, struct svcxprt_rdma, sc_xprt); - struct ib_device *device = rdma->sc_cm_id->device; + struct ib_device *device; might_sleep(); + if (!rdma->sc_cm_id) + goto out_free; + + device = rdma->sc_cm_id->device; + /* This blocks until the Completion Queues are empty */ if (rdma->sc_qp && !IS_ERR(rdma->sc_qp)) ib_drain_qp(rdma->sc_qp); @@ -662,11 +693,13 @@ static void svc_rdma_free(struct svc_xprt *xprt) if (rdma->sc_pd && !IS_ERR(rdma->sc_pd)) ib_dealloc_pd(rdma->sc_pd); + if (!test_bit(XPT_LISTENER, &rdma->sc_xprt.xpt_flags)) + rpcrdma_rn_unregister(device, &rdma->sc_rn); + /* Destroy the CM ID */ rdma_destroy_id(rdma->sc_cm_id); - if (!test_bit(XPT_LISTENER, &rdma->sc_xprt.xpt_flags)) - rpcrdma_rn_unregister(device, &rdma->sc_rn); +out_free: kfree(rdma); } diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c index 359407aae03e..5f7955e4c404 100644 --- a/net/sunrpc/xprtsock.c +++ b/net/sunrpc/xprtsock.c @@ -2648,7 +2648,17 @@ static int xs_tls_handshake_sync(struct rpc_xprt *lower_xprt, struct xprtsec_par rc = wait_for_completion_interruptible_timeout(&lower_transport->handshake_done, XS_TLS_HANDSHAKE_TO); if (rc <= 0) { - tls_handshake_cancel(sk); + if (!tls_handshake_cancel(sk)) { + /* + * Cancellation lost to handshake_complete(): the + * callback still owns its xprt reference and is in + * flight. Wait for it to finish before returning. + */ + wait_for_completion(&lower_transport->handshake_done); + if (rc == 0) + rc = -ETIMEDOUT; + goto out; + } if (rc == 0) rc = -ETIMEDOUT; goto out_put_xprt; diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c index 96c9fe8d357c..4f9aa9c4c3aa 100644 --- a/net/vmw_vsock/virtio_transport.c +++ b/net/vmw_vsock/virtio_transport.c @@ -872,10 +872,10 @@ static void virtio_vsock_remove(struct virtio_device *vdev) /* Other works can be queued before 'config->del_vqs()', so we flush * all works before to free the vsock object to avoid use after free. */ - flush_work(&vsock->rx_work); flush_work(&vsock->tx_work); flush_work(&vsock->event_work); flush_work(&vsock->send_pkt_work); + flush_work(&vsock->rx_work); mutex_unlock(&the_virtio_vsock_mutex); |
