diff options
Diffstat (limited to 'net')
208 files changed, 3027 insertions, 1470 deletions
diff --git a/net/9p/client.c b/net/9p/client.c index 9c9d249dabae..08ae4c44d730 100644 --- a/net/9p/client.c +++ b/net/9p/client.c @@ -723,6 +723,8 @@ again: if (err == -ERESTARTSYS && c->status == Connected && type == P9_TFLUSH) { + if (fatal_signal_pending(current)) + goto recalc_sigpending; sigpending = 1; clear_thread_flag(TIF_SIGPENDING); goto again; diff --git a/net/9p/trans_rdma.c b/net/9p/trans_rdma.c index b84748baf9cb..d9f53f1da2c2 100644 --- a/net/9p/trans_rdma.c +++ b/net/9p/trans_rdma.c @@ -240,25 +240,36 @@ p9_cm_event_handler(struct rdma_cm_id *id, struct rdma_cm_event *event) { struct p9_client *c = id->context; struct p9_trans_rdma *rdma = c->trans; + unsigned long flags; + switch (event->event) { case RDMA_CM_EVENT_ADDR_RESOLVED: + spin_lock_irqsave(&rdma->req_lock, flags); BUG_ON(rdma->state != P9_RDMA_INIT); rdma->state = P9_RDMA_ADDR_RESOLVED; + spin_unlock_irqrestore(&rdma->req_lock, flags); break; case RDMA_CM_EVENT_ROUTE_RESOLVED: + spin_lock_irqsave(&rdma->req_lock, flags); BUG_ON(rdma->state != P9_RDMA_ADDR_RESOLVED); rdma->state = P9_RDMA_ROUTE_RESOLVED; + spin_unlock_irqrestore(&rdma->req_lock, flags); break; case RDMA_CM_EVENT_ESTABLISHED: + spin_lock_irqsave(&rdma->req_lock, flags); BUG_ON(rdma->state != P9_RDMA_ROUTE_RESOLVED); rdma->state = P9_RDMA_CONNECTED; + spin_unlock_irqrestore(&rdma->req_lock, flags); break; case RDMA_CM_EVENT_DISCONNECTED: - if (rdma) + if (rdma) { + spin_lock_irqsave(&rdma->req_lock, flags); rdma->state = P9_RDMA_CLOSED; + spin_unlock_irqrestore(&rdma->req_lock, flags); + } c->status = Disconnected; break; @@ -296,6 +307,7 @@ recv_done(struct ib_cq *cq, struct ib_wc *wc) struct p9_req_t *req; int err = 0; int16_t tag; + unsigned long flags; req = NULL; ib_dma_unmap_single(rdma->cm_id->device, c->busa, client->msize, @@ -332,7 +344,10 @@ recv_done(struct ib_cq *cq, struct ib_wc *wc) err_out: p9_debug(P9_DEBUG_ERROR, "req %p err %d status %d\n", req, err, wc->status); - rdma->state = P9_RDMA_FLUSHING; + spin_lock_irqsave(&rdma->req_lock, flags); + if (rdma->state < P9_RDMA_FLUSHING) + rdma->state = P9_RDMA_FLUSHING; + spin_unlock_irqrestore(&rdma->req_lock, flags); client->status = Disconnected; goto out; } diff --git a/net/atm/common.c b/net/atm/common.c index c4edc1111bf0..f4c6e0131c92 100644 --- a/net/atm/common.c +++ b/net/atm/common.c @@ -720,6 +720,8 @@ static int atm_change_qos(struct atm_vcc *vcc, struct atm_qos *qos) static int check_tp(const struct atm_trafprm *tp) { /* @@@ Should be merged with adjust_tp */ + if (tp->traffic_class > ATM_ANYCLASS) + return -EINVAL; if (!tp->traffic_class || tp->traffic_class == ATM_ANYCLASS) return 0; if (tp->traffic_class != ATM_UBR && !tp->min_pcr && !tp->pcr && diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c index 860db505e869..c9ef48b5e69e 100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@ -545,7 +545,7 @@ static bool batadv_is_orig_node_eligible(struct batadv_dat_candidate *res, * the one with the lowest address */ if (tmp_max == max && max_orig_node && - batadv_compare_eth(candidate->orig, max_orig_node->orig)) + memcmp(candidate->orig, max_orig_node->orig, ETH_ALEN) >= 0) goto out; ret = true; @@ -1066,6 +1066,9 @@ out: * @skb: the buffer containing the packet to extract the VID from * @hdr_size: the size of the batman-adv header encapsulating the packet * + * The caller must ensure that at least @hdr_size + ETH_HLEN bytes are + * accessible after skb->data. + * * Return: If the packet embedded in the skb is vlan tagged this function * returns the VID with the BATADV_VLAN_HAS_TAG flag. Otherwise BATADV_NO_FLAGS * is returned. @@ -1148,6 +1151,10 @@ bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, if (!atomic_read(&bat_priv->distributed_arp_table)) goto out; + /* first, find out the vid. */ + if (!pskb_may_pull(skb, hdr_size + ETH_HLEN)) + goto out; + vid = batadv_dat_get_vid(skb, &hdr_size); type = batadv_arp_get_type(bat_priv, skb, hdr_size); @@ -1243,6 +1250,10 @@ bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, if (!atomic_read(&bat_priv->distributed_arp_table)) goto out; + /* first, find out the vid. */ + if (!pskb_may_pull(skb, hdr_size + ETH_HLEN)) + goto out; + vid = batadv_dat_get_vid(skb, &hdr_size); type = batadv_arp_get_type(bat_priv, skb, hdr_size); @@ -1305,6 +1316,10 @@ void batadv_dat_snoop_outgoing_arp_reply(struct batadv_priv *bat_priv, if (!atomic_read(&bat_priv->distributed_arp_table)) return; + /* first, find out the vid. */ + if (!pskb_may_pull(skb, hdr_size + ETH_HLEN)) + return; + vid = batadv_dat_get_vid(skb, &hdr_size); type = batadv_arp_get_type(bat_priv, skb, hdr_size); @@ -1353,6 +1368,10 @@ bool batadv_dat_snoop_incoming_arp_reply(struct batadv_priv *bat_priv, if (!atomic_read(&bat_priv->distributed_arp_table)) goto out; + /* first, find out the vid. */ + if (!pskb_may_pull(skb, hdr_size + ETH_HLEN)) + goto out; + vid = batadv_dat_get_vid(skb, &hdr_size); type = batadv_arp_get_type(bat_priv, skb, hdr_size); @@ -1747,6 +1766,7 @@ void batadv_dat_snoop_incoming_dhcp_ack(struct batadv_priv *bat_priv, struct ethhdr *ethhdr; __be32 ip_src, yiaddr; unsigned short vid; + int hdr_size_tmp; __be16 proto; u8 *hw_src; @@ -1763,8 +1783,10 @@ void batadv_dat_snoop_incoming_dhcp_ack(struct batadv_priv *bat_priv, if (!batadv_dat_check_dhcp_ack(skb, proto, &ip_src, chaddr, &yiaddr)) return; + hdr_size_tmp = hdr_size; + vid = batadv_dat_get_vid(skb, &hdr_size_tmp); + ethhdr = (struct ethhdr *)(skb->data + hdr_size); hw_src = ethhdr->h_source; - vid = batadv_dat_get_vid(skb, &hdr_size); batadv_dat_entry_add(bat_priv, yiaddr, chaddr, vid); batadv_dat_entry_add(bat_priv, ip_src, hw_src, vid); @@ -1804,6 +1826,10 @@ bool batadv_dat_drop_broadcast_packet(struct batadv_priv *bat_priv, if (batadv_forw_packet_is_rebroadcast(forw_packet)) goto out; + /* first, find out the vid. */ + if (!pskb_may_pull(forw_packet->skb, hdr_size + ETH_HLEN)) + goto out; + vid = batadv_dat_get_vid(forw_packet->skb, &hdr_size); type = batadv_arp_get_type(bat_priv, forw_packet->skb, hdr_size); diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c index 4779741e7273..c9eb27ded616 100644 --- a/net/batman-adv/fragmentation.c +++ b/net/batman-adv/fragmentation.c @@ -516,8 +516,10 @@ int batadv_frag_send_packet(struct sk_buff *skb, mtu = min_t(unsigned int, mtu, BATADV_FRAG_MAX_FRAG_SIZE); max_fragment_size = mtu - header_size; - if (skb->len == 0 || max_fragment_size == 0) - return -EINVAL; + if (skb->len == 0 || max_fragment_size == 0) { + ret = -EINVAL; + goto free_skb; + } num_fragments = (skb->len - 1) / max_fragment_size + 1; max_fragment_size = (skb->len - 1) / num_fragments + 1; @@ -543,7 +545,7 @@ int batadv_frag_send_packet(struct sk_buff *skb, */ if (skb_has_frag_list(skb) && __skb_linearize(skb)) { ret = -ENOMEM; - goto free_skb; + goto put_primary_if; } /* Create one header to be copied to all fragments */ diff --git a/net/batman-adv/gateway_client.c b/net/batman-adv/gateway_client.c index ff341c270d91..39d03f1cc019 100644 --- a/net/batman-adv/gateway_client.c +++ b/net/batman-adv/gateway_client.c @@ -683,12 +683,13 @@ bool batadv_gw_out_of_range(struct batadv_priv *bat_priv, struct batadv_gw_node *gw_node = NULL; struct batadv_gw_node *curr_gw = NULL; struct batadv_neigh_ifinfo *curr_ifinfo, *old_ifinfo; - struct ethhdr *ethhdr = (struct ethhdr *)skb->data; + struct ethhdr *ethhdr; bool out_of_range = false; u8 curr_tq_avg; unsigned short vid; vid = batadv_get_vid(skb, 0); + ethhdr = (struct ethhdr *)skb->data; if (is_multicast_ether_addr(ethhdr->h_dest)) goto out; diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index a4d33ee0fda5..b9b0bef44803 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -245,6 +245,7 @@ err_orig: void batadv_mesh_free(struct net_device *mesh_iface) { struct batadv_priv *bat_priv = netdev_priv(mesh_iface); + struct batadv_meshif_vlan *vlan; atomic_set(&bat_priv->mesh_state, BATADV_MESH_DEACTIVATING); @@ -259,6 +260,13 @@ void batadv_mesh_free(struct net_device *mesh_iface) batadv_mcast_free(bat_priv); + /* destroy the "untagged" VLAN */ + vlan = batadv_meshif_vlan_get(bat_priv, BATADV_NO_FLAGS); + if (vlan) { + batadv_meshif_destroy_vlan(bat_priv, vlan); + batadv_meshif_vlan_put(vlan); + } + /* Free the TT and the originator tables only after having terminated * all the other depending components which may use these structures for * their purposes. @@ -354,7 +362,7 @@ void batadv_skb_set_priority(struct sk_buff *skb, int offset) switch (ethhdr->h_proto) { case htons(ETH_P_8021Q): - vhdr = skb_header_pointer(skb, offset + sizeof(*vhdr), + vhdr = skb_header_pointer(skb, offset, sizeof(*vhdr), &vhdr_tmp); if (!vhdr) return; @@ -566,6 +574,9 @@ void batadv_recv_handler_unregister(u8 packet_type) * @skb: the buffer containing the packet * @header_len: length of the batman header preceding the ethernet header * + * The caller must ensure that at least @header_len + ETH_HLEN bytes are + * accessible after skb->data. + * * Return: VID with the BATADV_VLAN_HAS_TAG flag when the packet embedded in the * skb is vlan tagged. Otherwise BATADV_NO_FLAGS. */ diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index dcdc82f6af8c..0d598bf39bfc 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -193,6 +193,9 @@ static netdev_tx_t batadv_interface_tx(struct sk_buff *skb, if (atomic_read(&bat_priv->mesh_state) != BATADV_MESH_ACTIVE) goto dropped; + if (!pskb_may_pull(skb, ETH_HLEN)) + goto dropped; + /* reset control block to avoid left overs from previous users */ memset(skb->cb, 0, sizeof(struct batadv_skb_cb)); @@ -432,6 +435,7 @@ void batadv_interface_rx(struct net_device *mesh_iface, if (!pskb_may_pull(skb, VLAN_ETH_HLEN)) goto dropped; + ethhdr = eth_hdr(skb); vhdr = skb_vlan_eth_hdr(skb); /* drop batman-in-batman packets to prevent loops */ @@ -589,8 +593,8 @@ int batadv_meshif_create_vlan(struct batadv_priv *bat_priv, unsigned short vid) * @bat_priv: the bat priv with all the mesh interface information * @vlan: the object to remove */ -static void batadv_meshif_destroy_vlan(struct batadv_priv *bat_priv, - struct batadv_meshif_vlan *vlan) +void batadv_meshif_destroy_vlan(struct batadv_priv *bat_priv, + struct batadv_meshif_vlan *vlan) { /* explicitly remove the associated TT local entry because it is marked * with the NOPURGE flag @@ -1085,22 +1089,13 @@ static int batadv_meshif_newlink(struct net_device *dev, static void batadv_meshif_destroy_netlink(struct net_device *mesh_iface, struct list_head *head) { - struct batadv_priv *bat_priv = netdev_priv(mesh_iface); struct batadv_hard_iface *hard_iface; - struct batadv_meshif_vlan *vlan; while (!list_empty(&mesh_iface->adj_list.lower)) { hard_iface = netdev_adjacent_get_private(mesh_iface->adj_list.lower.next); batadv_hardif_disable_interface(hard_iface); } - /* destroy the "untagged" VLAN */ - vlan = batadv_meshif_vlan_get(bat_priv, BATADV_NO_FLAGS); - if (vlan) { - batadv_meshif_destroy_vlan(bat_priv, vlan); - batadv_meshif_vlan_put(vlan); - } - unregister_netdevice_queue(mesh_iface, head); } diff --git a/net/batman-adv/mesh-interface.h b/net/batman-adv/mesh-interface.h index 53756c5a45e0..5e1e83e04ffb 100644 --- a/net/batman-adv/mesh-interface.h +++ b/net/batman-adv/mesh-interface.h @@ -21,6 +21,8 @@ void batadv_interface_rx(struct net_device *mesh_iface, bool batadv_meshif_is_valid(const struct net_device *net_dev); extern struct rtnl_link_ops batadv_link_ops; int batadv_meshif_create_vlan(struct batadv_priv *bat_priv, unsigned short vid); +void batadv_meshif_destroy_vlan(struct batadv_priv *bat_priv, + struct batadv_meshif_vlan *vlan); void batadv_meshif_vlan_release(struct kref *ref); struct batadv_meshif_vlan *batadv_meshif_vlan_get(struct batadv_priv *bat_priv, unsigned short vid); diff --git a/net/batman-adv/multicast_forw.c b/net/batman-adv/multicast_forw.c index b8668a80b94a..1404a3b7adfb 100644 --- a/net/batman-adv/multicast_forw.c +++ b/net/batman-adv/multicast_forw.c @@ -927,11 +927,11 @@ static int batadv_mcast_forw_packet(struct batadv_priv *bat_priv, { struct batadv_tvlv_mcast_tracker *mcast_tracker; struct batadv_neigh_node *neigh_node; - unsigned long offset, num_dests_off; struct sk_buff *nexthop_skb; unsigned char *skb_net_hdr; bool local_recv = false; unsigned int tvlv_len; + unsigned long offset; bool xmitted = false; u8 *dest, *next_dest; u16 num_dests; @@ -940,9 +940,8 @@ static int batadv_mcast_forw_packet(struct batadv_priv *bat_priv, /* (at least) TVLV part needs to be linearized */ SKB_LINEAR_ASSERT(skb); - /* check if num_dests is within skb length */ - num_dests_off = offsetof(struct batadv_tvlv_mcast_tracker, num_dests); - if (num_dests_off > skb_network_header_len(skb)) + /* check if batadv_tvlv_mcast_tracker header is within skb length */ + if (sizeof(*mcast_tracker) > skb_network_header_len(skb)) return -EINVAL; skb_net_hdr = skb_network_header(skb); diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c index 41951c7a1c50..9de5bc552bfd 100644 --- a/net/batman-adv/routing.c +++ b/net/batman-adv/routing.c @@ -855,8 +855,8 @@ static bool batadv_check_unicast_ttvn(struct batadv_priv *bat_priv, if (skb_cow(skb, sizeof(*unicast_packet)) < 0) return false; - unicast_packet = (struct batadv_unicast_packet *)skb->data; vid = batadv_get_vid(skb, hdr_len); + unicast_packet = (struct batadv_unicast_packet *)skb->data; ethhdr = (struct ethhdr *)(skb->data + hdr_len); /* do not reroute multicast frames in a unicast header */ @@ -1029,6 +1029,7 @@ int batadv_recv_unicast_packet(struct sk_buff *skb, hdr_size); batadv_orig_node_put(orig_node_gw); if (is_gw) { + orig_addr_gw = eth_hdr(skb)->h_source; batadv_dbg(BATADV_DBG_BLA, bat_priv, "%s(): Dropped unicast pkt received from another backbone gw %pM.\n", __func__, orig_addr_gw); diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index 83dfd804a143..877475c68825 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -2963,7 +2963,7 @@ static bool batadv_send_tt_request(struct batadv_priv *bat_priv, out: batadv_hardif_put(primary_if); - if (ret && tt_req_node) { + if (!ret && tt_req_node) { spin_lock_bh(&bat_priv->tt.req_list_lock); if (!hlist_unhashed(&tt_req_node->list)) { hlist_del_init(&tt_req_node->list); @@ -4025,7 +4025,8 @@ static int batadv_tt_tvlv_unicast_handler_v1(struct batadv_priv *bat_priv, u16 tvlv_value_len) { struct batadv_tvlv_tt_data *tt_data; - u16 tt_vlan_len, tt_num_entries; + u16 tt_num_entries; + size_t tt_vlan_len; char tt_flag; bool ret; diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c index 5325c1b8dcd8..e5e84941c652 100644 --- a/net/bluetooth/6lowpan.c +++ b/net/bluetooth/6lowpan.c @@ -632,7 +632,7 @@ static struct l2cap_chan *chan_create(void) if (!chan) return NULL; - l2cap_chan_set_defaults(chan); + l2cap_chan_set_defaults(chan, NULL); chan->chan_type = L2CAP_CHAN_CONN_ORIENTED; chan->mode = L2CAP_MODE_LE_FLOWCTL; @@ -745,19 +745,24 @@ static inline void chan_ready_cb(struct l2cap_chan *chan) ifup(dev->netdev); } -static inline struct l2cap_chan *chan_new_conn_cb(struct l2cap_chan *pchan) +static void unregister_dev(struct lowpan_btle_dev *dev) { - struct l2cap_chan *chan; - - chan = chan_create(); - if (!chan) - return NULL; - - chan->ops = pchan->ops; - - BT_DBG("chan %p pchan %p", chan, pchan); - - return chan; + struct hci_dev *hdev = READ_ONCE(dev->hdev); + + /* If netdev holds last reference to hci_dev (its parent device), this + * leads to theoretical cyclic locking on lowpan_unregister_netdev: + * + * rtnl_lock -> put_device(parent) -> hci_release_dev -> + * destroy_workqueue -> hci_rx_work -> l2cap_recv_acldata -> + * chan_ready_cb -> ifup -> rtnl_lock + * + * However, hci_rx_work is disabled in hci_unregister_dev, so this + * should not occur. Make lockdep happy by postponing hdev release after + * netdev put. + */ + hci_dev_hold(hdev); + lowpan_unregister_netdev(dev->netdev); + hci_dev_put(hdev); } static void delete_netdev(struct work_struct *work) @@ -766,7 +771,7 @@ static void delete_netdev(struct work_struct *work) struct lowpan_btle_dev, delete_netdev); - lowpan_unregister_netdev(entry->netdev); + unregister_dev(entry); /* The entry pointer is deleted by the netdev destructor. */ } @@ -869,7 +874,6 @@ static long chan_get_sndtimeo_cb(struct l2cap_chan *chan) static const struct l2cap_ops bt_6lowpan_chan_ops = { .name = "L2CAP 6LoWPAN channel", - .new_connection = chan_new_conn_cb, .recv = chan_recv_cb, .close = chan_close_cb, .state_change = chan_state_change_cb, @@ -997,16 +1001,19 @@ static int get_l2cap_conn(char *buf, bdaddr_t *addr, u8 *addr_type, hci_dev_lock(hdev); hcon = hci_conn_hash_lookup_le(hdev, addr, le_addr_type); - hci_dev_unlock(hdev); - hci_dev_put(hdev); - - if (!hcon) + if (!hcon) { + hci_dev_unlock(hdev); + hci_dev_put(hdev); return -ENOENT; + } - *conn = (struct l2cap_conn *)hcon->l2cap_data; + *conn = l2cap_conn_hold_unless_zero(hcon->l2cap_data); BT_DBG("conn %p dst %pMR type %u", *conn, &hcon->dst, hcon->dst_type); + hci_dev_unlock(hdev); + hci_dev_put(hdev); + return 0; } @@ -1153,18 +1160,22 @@ static ssize_t lowpan_control_write(struct file *fp, if (conn) { struct lowpan_peer *peer; - if (!is_bt_6lowpan(conn->hcon)) + if (!is_bt_6lowpan(conn->hcon)) { + l2cap_conn_put(conn); return -EINVAL; + } peer = lookup_peer(conn); if (peer) { BT_DBG("6LoWPAN connection already exists"); + l2cap_conn_put(conn); return -EALREADY; } BT_DBG("conn %p dst %pMR type %d user %u", conn, &conn->hcon->dst, conn->hcon->dst_type, addr_type); + l2cap_conn_put(conn); } ret = bt_6lowpan_connect(&addr, addr_type); @@ -1180,6 +1191,8 @@ static ssize_t lowpan_control_write(struct file *fp, return ret; ret = bt_6lowpan_disconnect(conn, addr_type); + if (conn) + l2cap_conn_put(conn); if (ret < 0) return ret; @@ -1240,6 +1253,7 @@ static void disconnect_devices(void) break; new_dev->netdev = entry->netdev; + new_dev->hdev = entry->hdev; INIT_LIST_HEAD(&new_dev->list); list_add_rcu(&new_dev->list, &devices); @@ -1251,7 +1265,7 @@ static void disconnect_devices(void) ifdown(entry->netdev); BT_DBG("Unregistering netdev %s %p", entry->netdev->name, entry->netdev); - lowpan_unregister_netdev(entry->netdev); + unregister_dev(entry); kfree(entry); } } diff --git a/net/bluetooth/eir.c b/net/bluetooth/eir.c index 3f72111ba651..1de5f9df6eec 100644 --- a/net/bluetooth/eir.c +++ b/net/bluetooth/eir.c @@ -283,10 +283,12 @@ u8 eir_create_adv_data(struct hci_dev *hdev, u8 instance, u8 *ptr, u8 size) if (!flags) flags |= mgmt_get_adv_discov_flags(hdev); - /* If flags would still be empty, then there is no need to - * include the "Flags" AD field". + /* Only add the "Flags" if it fits together with the instance + * advertising data; drop it rather than overflow the buffer. */ - if (flags && (ad_len + eir_precalc_len(1) <= size)) { + if (flags && + (ad_len + eir_precalc_len(1) + + (adv ? adv->adv_data_len : 0) <= size)) { ptr[0] = 0x02; ptr[1] = EIR_FLAGS; ptr[2] = flags; diff --git a/net/bluetooth/hci_codec.c b/net/bluetooth/hci_codec.c index 3cc135bb1d30..5bc5003c387c 100644 --- a/net/bluetooth/hci_codec.c +++ b/net/bluetooth/hci_codec.c @@ -100,7 +100,7 @@ static void hci_read_codec_capabilities(struct hci_dev *hdev, __u8 transport, caps = (void *)skb->data; if (skb->len < sizeof(*caps)) goto error; - if (skb->len < caps->len) + if (skb->len < sizeof(caps->len) + caps->len) goto error; len += sizeof(caps->len) + caps->len; skb_pull(skb, sizeof(caps->len) + caps->len); diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 0f86b81b3973..a1d7e7985f76 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2707,6 +2707,8 @@ void hci_unregister_dev(struct hci_dev *hdev) disable_work_sync(&hdev->tx_work); disable_work_sync(&hdev->power_on); disable_work_sync(&hdev->error_reset); + disable_delayed_work_sync(&hdev->cmd_timer); + disable_delayed_work_sync(&hdev->ncmd_timer); hci_cmd_sync_clear(hdev); diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index b6b52b81b7b9..396aafa609c9 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -1765,6 +1765,13 @@ static void le_set_scan_enable_complete(struct hci_dev *hdev, u8 enable) hci_dev_clear_flag(hdev, HCI_LE_SCAN); + if (hdev->discovery.type == DISCOV_TYPE_INTERLEAVED && + hci_test_quirk(hdev, HCI_QUIRK_SIMULTANEOUS_DISCOVERY) && + !test_bit(HCI_INQUIRY, &hdev->flags) && + hdev->discovery.state == DISCOVERY_FINDING) { + hci_discovery_set_state(hdev, DISCOVERY_STOPPED); + } + /* The HCI_LE_SCAN_INTERRUPTED flag indicates that we * interrupted scanning due to a connect request. Mark * therefore discovery as stopped. diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 908198959e3b..e04948fb6832 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -525,7 +525,10 @@ void l2cap_chan_put(struct l2cap_chan *c) } EXPORT_SYMBOL_GPL(l2cap_chan_put); -void l2cap_chan_set_defaults(struct l2cap_chan *chan) +/* Initialise @chan with default values, inheriting from the parent channel + * @pchan when it is given. + */ +void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan) { chan->fcs = L2CAP_FCS_CRC16; chan->max_tx = L2CAP_DEFAULT_MAX_TX; @@ -539,6 +542,31 @@ void l2cap_chan_set_defaults(struct l2cap_chan *chan) chan->retrans_timeout = L2CAP_DEFAULT_RETRANS_TO; chan->monitor_timeout = L2CAP_DEFAULT_MONITOR_TO; + if (pchan) { + BT_DBG("chan %p pchan %p", chan, pchan); + + chan->chan_type = pchan->chan_type; + chan->imtu = pchan->imtu; + chan->omtu = pchan->omtu; + chan->mode = pchan->mode; + chan->fcs = pchan->fcs; + chan->max_tx = pchan->max_tx; + chan->tx_win = pchan->tx_win; + chan->tx_win_max = pchan->tx_win_max; + chan->sec_level = pchan->sec_level; + chan->conf_state = pchan->conf_state; + chan->flags = pchan->flags; + chan->tx_credits = pchan->tx_credits; + chan->rx_credits = pchan->rx_credits; + + if (chan->chan_type == L2CAP_CHAN_FIXED) { + chan->scid = pchan->scid; + chan->dcid = pchan->scid; + } + + return; + } + chan->conf_state = 0; set_bit(CONF_NOT_COMPLETE, &chan->conf_state); @@ -4027,6 +4055,38 @@ static inline int l2cap_command_rej(struct l2cap_conn *conn, return 0; } +/* Allocate and initialise a channel for an incoming connection. + * + * The channel inherits its configuration from @pchan and is linked into @conn + * before ->new_connection() runs, so the conn list reference keeps it alive if + * the callback exposes it (e.g. via the socket accept queue) before this + * returns. The l2cap_chan_create() reference is taken over by the subsystem on + * success and dropped here on failure. + */ +static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn, + struct l2cap_chan *pchan) +{ + struct l2cap_chan *chan; + + chan = l2cap_chan_create(); + if (!chan) + return NULL; + + l2cap_chan_set_defaults(chan, pchan); + chan->ops = pchan->ops; + + __l2cap_chan_add(conn, chan); + + if (pchan->ops->new_connection && + pchan->ops->new_connection(pchan, chan) < 0) { + l2cap_chan_del(chan, 0); + l2cap_chan_put(chan); + return NULL; + } + + return chan; +} + static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data, u8 rsp_code) { @@ -4073,7 +4133,7 @@ static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, goto response; } - chan = pchan->ops->new_connection(pchan); + chan = l2cap_new_connection(conn, pchan); if (!chan) goto response; @@ -4091,8 +4151,6 @@ static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, chan->psm = psm; chan->dcid = scid; - __l2cap_chan_add(conn, chan); - dcid = chan->scid; __set_chan_timer(chan, chan->ops->get_sndtimeo(chan)); @@ -4832,6 +4890,7 @@ static void l2cap_put_ident(struct l2cap_conn *conn, u8 code, u8 id) case L2CAP_ECHO_RSP: case L2CAP_INFO_RSP: case L2CAP_CONN_PARAM_UPDATE_RSP: + case L2CAP_LE_CONN_RSP: case L2CAP_ECRED_CONN_RSP: case L2CAP_ECRED_RECONF_RSP: /* First do a lookup since the remote may send bogus ids that @@ -4983,7 +5042,7 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn, goto response_unlock; } - chan = pchan->ops->new_connection(pchan); + chan = l2cap_new_connection(conn, pchan); if (!chan) { result = L2CAP_CR_LE_NO_MEM; goto response_unlock; @@ -4998,8 +5057,6 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn, chan->omtu = mtu; chan->remote_mps = mps; - __l2cap_chan_add(conn, chan); - l2cap_le_flowctl_init(chan, __le16_to_cpu(req->credits)); dcid = chan->scid; @@ -5207,7 +5264,7 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn, continue; } - chan = pchan->ops->new_connection(pchan); + chan = l2cap_new_connection(conn, pchan); if (!chan) { result = L2CAP_CR_LE_NO_MEM; continue; @@ -5222,8 +5279,6 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn, chan->omtu = mtu; chan->remote_mps = mps; - __l2cap_chan_add(conn, chan); - l2cap_ecred_init(chan, __le16_to_cpu(req->credits)); /* Init response */ @@ -6729,6 +6784,7 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan) struct l2cap_conn *conn = chan->conn; struct l2cap_le_credits pkt; u16 return_credits = l2cap_le_rx_credits(chan); + int ident; if (chan->mode != L2CAP_MODE_LE_FLOWCTL && chan->mode != L2CAP_MODE_EXT_FLOWCTL) @@ -6746,9 +6802,18 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan) pkt.cid = cpu_to_le16(chan->scid); pkt.credits = cpu_to_le16(return_credits); - chan->ident = l2cap_get_ident(conn); + ident = l2cap_get_ident(conn); + + l2cap_send_cmd(conn, ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt); - l2cap_send_cmd(conn, chan->ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt); + /* L2CAP_LE_CREDITS has no response so the ident is never released by + * l2cap_put_ident() - release it right away, otherwise the tx_ida + * range is exhausted after 254 packets and from then on credits are + * sent with the invalid ident 0, which some remote stacks ignore, + * stalling the channel. + */ + if (ident > 0) + ida_free(&conn->tx_ida, ident); } void l2cap_chan_rx_avail(struct l2cap_chan *chan, ssize_t rx_avail) @@ -7491,14 +7556,12 @@ static void l2cap_connect_cfm(struct hci_conn *hcon, u8 status) goto next; l2cap_chan_lock(pchan); - chan = pchan->ops->new_connection(pchan); + chan = l2cap_new_connection(conn, pchan); if (chan) { bacpy(&chan->src, &hcon->src); bacpy(&chan->dst, &hcon->dst); chan->src_type = bdaddr_src_type(hcon); chan->dst_type = dst_type; - - __l2cap_chan_add(conn, chan); } l2cap_chan_unlock(pchan); @@ -7715,6 +7778,7 @@ struct l2cap_conn *l2cap_conn_hold_unless_zero(struct l2cap_conn *c) return c; } +EXPORT_SYMBOL(l2cap_conn_hold_unless_zero); int l2cap_recv_acldata(struct hci_dev *hdev, u16 handle, struct sk_buff *skb, u16 flags) diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index f2fa3130fd41..1a916c0f40cc 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -45,7 +45,8 @@ static struct bt_sock_list l2cap_sk_list = { static const struct proto_ops l2cap_sock_ops; static void l2cap_sock_init(struct sock *sk, struct sock *parent); static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, - int proto, gfp_t prio, int kern); + int proto, gfp_t prio, int kern, + struct l2cap_chan *chan); static void l2cap_sock_cleanup_listen(struct sock *parent); bool l2cap_is_socket(struct socket *sock) @@ -1256,6 +1257,23 @@ done: return err; } +/* Release the sock's ref on chan and clear the pointer so that the ref is + * dropped exactly once even if both l2cap_sock_kill() and + * l2cap_sock_destruct() run. Setting chan->data to NULL first stops any other + * task from dereferencing the now-dead sock pointer. + */ +static void l2cap_sock_put_chan(struct sock *sk) +{ + struct l2cap_chan *chan = l2cap_pi(sk)->chan; + + if (!chan) + return; + + chan->data = NULL; + l2cap_pi(sk)->chan = NULL; + l2cap_chan_put(chan); +} + /* Kill socket (only if zapped and orphan) * Must be called on unlocked socket, with l2cap channel lock. */ @@ -1266,13 +1284,9 @@ static void l2cap_sock_kill(struct sock *sk) BT_DBG("sk %p state %s", sk, state_to_string(sk->sk_state)); - /* Sock is dead, so set chan data to NULL, avoid other task use invalid - * sock pointer. - */ - l2cap_pi(sk)->chan->data = NULL; - /* Kill poor orphan */ + l2cap_sock_put_chan(sk); - l2cap_chan_put(l2cap_pi(sk)->chan); + /* Kill poor orphan */ sock_set_flag(sk, SOCK_DEAD); sock_put(sk); } @@ -1515,12 +1529,13 @@ static void l2cap_sock_cleanup_listen(struct sock *parent) } } -static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan) +static int l2cap_sock_new_connection_cb(struct l2cap_chan *chan, + struct l2cap_chan *new_chan) { struct sock *sk, *parent = chan->data; if (!parent) - return NULL; + return -EINVAL; lock_sock(parent); @@ -1528,25 +1543,28 @@ static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan) if (sk_acceptq_is_full(parent)) { BT_DBG("backlog full %d", parent->sk_ack_backlog); release_sock(parent); - return NULL; + return -ENOBUFS; } sk = l2cap_sock_alloc(sock_net(parent), NULL, BTPROTO_L2CAP, - GFP_ATOMIC, 0); + GFP_ATOMIC, 0, new_chan); if (!sk) { release_sock(parent); - return NULL; - } + return -ENOMEM; + } bt_sock_reclassify_lock(sk, BTPROTO_L2CAP); l2cap_sock_init(sk, parent); + /* The conn list reference taken by l2cap_new_connection() keeps new_chan + * alive once release_sock() lets another task free this socket. + */ bt_accept_enqueue(parent, sk, false); release_sock(parent); - return l2cap_pi(sk)->chan; + return 0; } static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb) @@ -1844,10 +1862,7 @@ static void l2cap_sock_destruct(struct sock *sk) BT_DBG("sk %p", sk); - if (l2cap_pi(sk)->chan) { - l2cap_pi(sk)->chan->data = NULL; - l2cap_chan_put(l2cap_pi(sk)->chan); - } + l2cap_sock_put_chan(sk); list_for_each_entry_safe(rx_busy, next, &l2cap_pi(sk)->rx_busy, list) { kfree_skb(rx_busy->skb); @@ -1880,30 +1895,12 @@ static void l2cap_sock_init(struct sock *sk, struct sock *parent) BT_DBG("sk %p", sk); if (parent) { - struct l2cap_chan *pchan = l2cap_pi(parent)->chan; - sk->sk_type = parent->sk_type; bt_sk(sk)->flags = bt_sk(parent)->flags; - chan->chan_type = pchan->chan_type; - chan->imtu = pchan->imtu; - chan->omtu = pchan->omtu; - chan->conf_state = pchan->conf_state; - chan->mode = pchan->mode; - chan->fcs = pchan->fcs; - chan->max_tx = pchan->max_tx; - chan->tx_win = pchan->tx_win; - chan->tx_win_max = pchan->tx_win_max; - chan->sec_level = pchan->sec_level; - chan->flags = pchan->flags; - chan->tx_credits = pchan->tx_credits; - chan->rx_credits = pchan->rx_credits; - - if (chan->chan_type == L2CAP_CHAN_FIXED) { - chan->scid = pchan->scid; - chan->dcid = pchan->scid; - } - + /* Channel configuration is inherited from the parent by + * l2cap_new_connection(). + */ security_sk_clone(parent, sk); } else { switch (sk->sk_type) { @@ -1929,7 +1926,7 @@ static void l2cap_sock_init(struct sock *sk, struct sock *parent) chan->mode = L2CAP_MODE_BASIC; } - l2cap_chan_set_defaults(chan); + l2cap_chan_set_defaults(chan, NULL); } /* Default config options */ @@ -1948,10 +1945,10 @@ static struct proto l2cap_proto = { }; static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, - int proto, gfp_t prio, int kern) + int proto, gfp_t prio, int kern, + struct l2cap_chan *chan) { struct sock *sk; - struct l2cap_chan *chan; sk = bt_sock_alloc(net, sock, &l2cap_proto, proto, prio, kern); if (!sk) @@ -1962,16 +1959,7 @@ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, INIT_LIST_HEAD(&l2cap_pi(sk)->rx_busy); - chan = l2cap_chan_create(); - if (!chan) { - sk_free(sk); - if (sock) - sock->sk = NULL; - return NULL; - } - - l2cap_chan_hold(chan); - + /* The sock takes ownership of the caller's reference on chan. */ l2cap_pi(sk)->chan = chan; return sk; @@ -1981,6 +1969,7 @@ static int l2cap_sock_create(struct net *net, struct socket *sock, int protocol, int kern) { struct sock *sk; + struct l2cap_chan *chan; BT_DBG("sock %p", sock); @@ -1995,10 +1984,16 @@ static int l2cap_sock_create(struct net *net, struct socket *sock, int protocol, sock->ops = &l2cap_sock_ops; - sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern); - if (!sk) + chan = l2cap_chan_create(); + if (!chan) return -ENOMEM; + sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern, chan); + if (!sk) { + l2cap_chan_put(chan); + return -ENOMEM; + } + l2cap_sock_init(sk, NULL); bt_sock_link(&l2cap_sk_list, sk); return 0; diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 90da285a00cd..410910f64c1c 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -5296,6 +5296,8 @@ static void mgmt_add_adv_patterns_monitor_complete(struct hci_dev *hdev, if (monitor->state == ADV_MONITOR_STATE_NOT_REGISTERED) monitor->state = ADV_MONITOR_STATE_REGISTERED; hci_update_passive_scan(hdev); + } else { + hci_free_adv_monitor(hdev, monitor); } mgmt_cmd_complete(cmd->sk, cmd->hdev->id, cmd->opcode, diff --git a/net/bluetooth/msft.c b/net/bluetooth/msft.c index c560d8467669..dea81c8e8b20 100644 --- a/net/bluetooth/msft.c +++ b/net/bluetooth/msft.c @@ -291,7 +291,7 @@ static int msft_le_monitor_advertisement_cb(struct hci_dev *hdev, u16 opcode, monitor->state = ADV_MONITOR_STATE_OFFLOADED; unlock: - if (status) + if (status && msft->resuming) hci_free_adv_monitor(hdev, monitor); hci_dev_unlock(hdev); diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index a19ae1b39bc0..285a98594ba4 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -482,9 +482,13 @@ static struct sock *sco_get_sock_listen(bdaddr_t *src) sk1 = sk; } + sk = sk ? sk : sk1; + if (sk) + sock_hold(sk); + read_unlock(&sco_sk_list.lock); - return sk ? sk : sk1; + return sk; } static void sco_sock_destruct(struct sock *sk) @@ -527,11 +531,13 @@ static void sco_sock_kill(struct sock *sk) BT_DBG("sk %p state %d", sk, sk->sk_state); /* Sock is dead, so set conn->sk to NULL to avoid possible UAF */ + lock_sock(sk); if (sco_pi(sk)->conn) { sco_conn_lock(sco_pi(sk)->conn); sco_pi(sk)->conn->sk = NULL; sco_conn_unlock(sco_pi(sk)->conn); } + release_sock(sk); /* Kill poor orphan */ bt_sock_unlink(&sco_sk_list, sk); @@ -566,10 +572,23 @@ static void __sco_sock_close(struct sock *sk) /* Must be called on unlocked socket. */ static void sco_sock_close(struct sock *sk) { + struct sco_conn *conn; + + lock_sock(sk); + conn = sco_pi(sk)->conn; + if (conn) + sco_conn_hold(conn); + release_sock(sk); + + if (conn) + disable_delayed_work_sync(&conn->timeout_work); + lock_sock(sk); - sco_sock_clear_timer(sk); __sco_sock_close(sk); release_sock(sk); + + if (conn) + sco_conn_put(conn); } static void sco_sock_init(struct sock *sk, struct sock *parent) @@ -1382,40 +1401,51 @@ static int sco_sock_release(struct socket *sock) static void sco_conn_ready(struct sco_conn *conn) { - struct sock *parent; - struct sock *sk = conn->sk; + struct sock *parent, *sk; + + sco_conn_lock(conn); + sk = sco_sock_hold(conn); + sco_conn_unlock(conn); BT_DBG("conn %p", conn); if (sk) { lock_sock(sk); - sco_sock_clear_timer(sk); - sk->sk_state = BT_CONNECTED; - sk->sk_state_change(sk); + + /* conn->sk may have become NULL if racing with sk close, but + * due to held hdev->lock, it can't become different sk. + */ + if (conn->sk) { + sco_sock_clear_timer(sk); + sk->sk_state = BT_CONNECTED; + sk->sk_state_change(sk); + } + release_sock(sk); + sock_put(sk); } else { - sco_conn_lock(conn); - - if (!conn->hcon) { - sco_conn_unlock(conn); + if (!conn->hcon) return; - } + + lockdep_assert_held(&conn->hcon->hdev->lock); parent = sco_get_sock_listen(&conn->hcon->src); - if (!parent) { - sco_conn_unlock(conn); + if (!parent) return; - } lock_sock(parent); + sco_conn_lock(conn); + + /* hdev->lock guarantees conn->sk == NULL still here */ + + if (parent->sk_state != BT_LISTEN) + goto release; + sk = sco_sock_alloc(sock_net(parent), NULL, BTPROTO_SCO, GFP_ATOMIC, 0); - if (!sk) { - release_sock(parent); - sco_conn_unlock(conn); - return; - } + if (!sk) + goto release; sco_sock_init(sk, parent); @@ -1434,9 +1464,10 @@ static void sco_conn_ready(struct sco_conn *conn) /* Wake up parent */ parent->sk_data_ready(parent); - release_sock(parent); - +release: sco_conn_unlock(conn); + release_sock(parent); + sock_put(parent); } } diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c index abf3ab7479ff..1172df9b7004 100644 --- a/net/bluetooth/smp.c +++ b/net/bluetooth/smp.c @@ -3234,34 +3234,19 @@ static const struct l2cap_ops smp_chan_ops = { .get_sndtimeo = l2cap_chan_no_get_sndtimeo, }; -static inline struct l2cap_chan *smp_new_conn_cb(struct l2cap_chan *pchan) +static inline int smp_new_conn_cb(struct l2cap_chan *chan, + struct l2cap_chan *new_chan) { - struct l2cap_chan *chan; - - BT_DBG("pchan %p", pchan); - - chan = l2cap_chan_create(); - if (!chan) - return NULL; - - chan->chan_type = pchan->chan_type; - chan->ops = &smp_chan_ops; - chan->scid = pchan->scid; - chan->dcid = chan->scid; - chan->imtu = pchan->imtu; - chan->omtu = pchan->omtu; - chan->mode = pchan->mode; + new_chan->ops = &smp_chan_ops; /* Other L2CAP channels may request SMP routines in order to * change the security level. This means that the SMP channel * lock must be considered in its own category to avoid lockdep * warnings. */ - atomic_set(&chan->nesting, L2CAP_NESTING_SMP); - - BT_DBG("created chan %p", chan); + atomic_set(&new_chan->nesting, L2CAP_NESTING_SMP); - return chan; + return 0; } static const struct l2cap_ops smp_root_chan_ops = { @@ -3332,7 +3317,7 @@ create_chan: l2cap_add_scid(chan, cid); - l2cap_chan_set_defaults(chan); + l2cap_chan_set_defaults(chan, NULL); if (cid == L2CAP_CID_SMP) { u8 bdaddr_type; diff --git a/net/bridge/br_cfm.c b/net/bridge/br_cfm.c index f4ca77d9b0e9..e1de280bacc1 100644 --- a/net/bridge/br_cfm.c +++ b/net/bridge/br_cfm.c @@ -805,6 +805,12 @@ int br_cfm_cc_ccm_tx(struct net_bridge *br, const u32 instance, goto save; } + if (!interval_to_us(mep->cc_config.exp_interval)) { + NL_SET_ERR_MSG_MOD(extack, + "Invalid CCM interval"); + return -EINVAL; + } + /* Start delayed work to transmit CCM frames. It is done with zero delay * to send first frame immediately */ diff --git a/net/bridge/br_cfm_netlink.c b/net/bridge/br_cfm_netlink.c index 2faab44652e7..91b9922dc3f2 100644 --- a/net/bridge/br_cfm_netlink.c +++ b/net/bridge/br_cfm_netlink.c @@ -34,7 +34,9 @@ br_cfm_cc_config_policy[IFLA_BRIDGE_CFM_CC_CONFIG_MAX + 1] = { [IFLA_BRIDGE_CFM_CC_CONFIG_UNSPEC] = { .type = NLA_REJECT }, [IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE] = { .type = NLA_U32 }, [IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE] = { .type = NLA_U32 }, - [IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL] = { .type = NLA_U32 }, + [IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL] = + NLA_POLICY_RANGE(NLA_U32, BR_CFM_CCM_INTERVAL_3_3_MS, + BR_CFM_CCM_INTERVAL_10_MIN), [IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID] = { .type = NLA_BINARY, .len = CFM_MAID_LENGTH }, }; diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c index ca3a637d7cca..5bcd1acc5da9 100644 --- a/net/bridge/br_if.c +++ b/net/bridge/br_if.c @@ -391,6 +391,9 @@ void br_dev_delete(struct net_device *dev, struct list_head *head) br_fdb_delete_by_port(br, NULL, 0, 1); + timer_shutdown_sync(&br->hello_timer); + timer_shutdown_sync(&br->topology_change_timer); + timer_shutdown_sync(&br->tcn_timer); cancel_delayed_work_sync(&br->gc_work); br_sysfs_delbr(br->dev); diff --git a/net/bridge/br_stp.c b/net/bridge/br_stp.c index 024210f95468..76a2df165ffc 100644 --- a/net/bridge/br_stp.c +++ b/net/bridge/br_stp.c @@ -371,7 +371,8 @@ void br_topology_change_detection(struct net_bridge *br) { int isroot = br_is_root_bridge(br); - if (br->stp_enabled != BR_KERNEL_STP) + if (br->stp_enabled != BR_KERNEL_STP || + !(br->dev->flags & IFF_UP)) return; br_info(br, "topology change detected, %s\n", diff --git a/net/bridge/netfilter/nft_meta_bridge.c b/net/bridge/netfilter/nft_meta_bridge.c index 1bcef43b2a81..a3d0c8a82d86 100644 --- a/net/bridge/netfilter/nft_meta_bridge.c +++ b/net/bridge/netfilter/nft_meta_bridge.c @@ -44,7 +44,9 @@ static void nft_meta_bridge_get_eval(const struct nft_expr *expr, if (!br_dev || !br_vlan_enabled(br_dev)) goto err; - br_vlan_get_pvid_rcu(in, &p_pvid); + if (br_vlan_get_pvid_rcu(in, &p_pvid)) + goto err; + nft_reg_store16(dest, p_pvid); return; } @@ -107,12 +109,30 @@ static int nft_meta_bridge_get_init(const struct nft_ctx *ctx, NULL, NFT_DATA_VALUE, len); } +static int nft_meta_bridge_get_validate(const struct nft_ctx *ctx, + const struct nft_expr *expr) +{ + struct nft_meta *priv = nft_expr_priv(expr); + unsigned int hooks; + + switch (priv->key) { + case NFT_META_BRI_IIFHWADDR: + hooks = 1 << NF_BR_PRE_ROUTING; + break; + default: + return nft_meta_get_validate(ctx, expr); + } + + return nft_chain_validate_hooks(ctx->chain, hooks); +} + static struct nft_expr_type nft_meta_bridge_type; static const struct nft_expr_ops nft_meta_bridge_get_ops = { .type = &nft_meta_bridge_type, .size = NFT_EXPR_SIZE(sizeof(struct nft_meta)), .eval = nft_meta_bridge_get_eval, .init = nft_meta_bridge_get_init, + .validate = nft_meta_bridge_get_validate, .dump = nft_meta_get_dump, .reduce = nft_meta_get_reduce, }; @@ -187,7 +207,6 @@ static int nft_meta_bridge_set_validate(const struct nft_ctx *ctx, switch (priv->key) { case NFT_META_BRI_BROUTE: - case NFT_META_BRI_IIFHWADDR: hooks = 1 << NF_BR_PRE_ROUTING; break; default: diff --git a/net/can/bcm.c b/net/can/bcm.c index 756acfd20c6c..a40519638a26 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -58,6 +58,7 @@ #include <linux/can/skb.h> #include <linux/can/bcm.h> #include <linux/slab.h> +#include <linux/workqueue.h> #include <linux/spinlock.h> #include <net/sock.h> #include <net/net_namespace.h> @@ -91,6 +92,8 @@ MODULE_ALIAS("can-proto-2"); #define BCM_MIN_NAMELEN CAN_REQUIRED_SIZE(struct sockaddr_can, can_ifindex) +static struct workqueue_struct *bcm_wq; + /* * easy access to the first 64 bit of can(fd)_frame payload. cp->data is * 64 bit aligned so the offset has to be multiples of 8 which is ensured @@ -104,6 +107,7 @@ static inline u64 get_u64(const struct canfd_frame *cp, int offset) struct bcm_op { struct list_head list; struct rcu_head rcu; + struct work_struct work; int ifindex; canid_t can_id; u32 flags; @@ -256,7 +260,7 @@ static int bcm_proc_show(struct seq_file *m, void *v) (reduction == 100) ? "near " : "", reduction); } - list_for_each_entry(op, &bo->tx_ops, list) { + list_for_each_entry_rcu(op, &bo->tx_ops, list) { seq_printf(m, "tx_op: %03X %s ", op->can_id, bcm_proc_getifname(net, ifname, op->ifindex)); @@ -788,9 +792,12 @@ static struct bcm_op *bcm_find_op(struct list_head *ops, return NULL; } -static void bcm_free_op_rcu(struct rcu_head *rcu_head) +static void bcm_free_op_work(struct work_struct *work) { - struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu); + struct bcm_op *op = container_of(work, struct bcm_op, work); + + hrtimer_cancel(&op->timer); + hrtimer_cancel(&op->thrtimer); if ((op->frames) && (op->frames != &op->sframe)) kfree(op->frames); @@ -798,9 +805,23 @@ static void bcm_free_op_rcu(struct rcu_head *rcu_head) if ((op->last_frames) && (op->last_frames != &op->last_sframe)) kfree(op->last_frames); + /* the last possible access to op->timer/op->thrtimer has now + * happened above via hrtimer_cancel() - op->sk is no longer + * needed by any pending timer callback, so drop our reference + */ + sock_put(op->sk); + kfree(op); } +static void bcm_free_op_rcu(struct rcu_head *rcu_head) +{ + struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu); + + INIT_WORK(&op->work, bcm_free_op_work); + queue_work(bcm_wq, &op->work); +} + static void bcm_remove_op(struct bcm_op *op) { hrtimer_cancel(&op->timer); @@ -925,6 +946,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, struct bcm_sock *bo = bcm_sk(sk); struct bcm_op *op; struct canfd_frame *cf; + bool add_op_to_list = false; unsigned int i; int err; @@ -1055,6 +1077,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, /* bcm_can_tx / bcm_tx_timeout_handler needs this */ op->sk = sk; + sock_hold(sk); op->ifindex = ifindex; /* initialize uninitialized (kzalloc) structure */ @@ -1065,8 +1088,7 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, hrtimer_setup(&op->thrtimer, hrtimer_dummy_timeout, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); - /* add this bcm_op to the list of the tx_ops */ - list_add(&op->list, &bo->tx_ops); + add_op_to_list = true; } /* if ((op = bcm_find_op(&bo->tx_ops, msg_head->can_id, ifindex))) */ @@ -1088,6 +1110,10 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, op->flags |= TX_ANNOUNCE; } + /* add this bcm_op to the list of the tx_ops? */ + if (add_op_to_list) + list_add_rcu(&op->list, &bo->tx_ops); + if (op->flags & TX_ANNOUNCE) bcm_can_tx(op); @@ -1216,6 +1242,7 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, /* bcm_can_tx / bcm_tx_timeout_handler needs this */ op->sk = sk; + sock_hold(sk); op->ifindex = ifindex; /* ifindex for timeout events w/o previous frame reception */ @@ -1227,9 +1254,6 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, hrtimer_setup(&op->thrtimer, bcm_rx_thr_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); - /* add this bcm_op to the list of the rx_ops */ - list_add(&op->list, &bo->rx_ops); - /* call can_rx_register() */ do_rx_register = 1; @@ -1295,6 +1319,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, op->rx_reg_dev = dev; dev_put(dev); + } else { + /* the requested device is gone - do not + * silently succeed without registering + */ + err = -ENODEV; } } else @@ -1303,10 +1332,12 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, bcm_rx_handler, op, "bcm", sk); if (err) { /* this bcm rx op is broken -> remove it */ - list_del_rcu(&op->list); bcm_remove_op(op); return err; } + + /* add this bcm_op to the list of the rx_ops */ + list_add_rcu(&op->list, &bo->rx_ops); } return msg_head->nframes * op->cfsiz + MHSIZ; @@ -1364,12 +1395,13 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) { struct sock *sk = sock->sk; struct bcm_sock *bo = bcm_sk(sk); - int ifindex = bo->ifindex; /* default ifindex for this bcm_op */ + int ifindex; struct bcm_msg_head msg_head; int cfsiz; int ret; /* read bytes or error codes as return value */ - if (!bo->bound) + /* Lockless fast-path check for bound socket */ + if (!READ_ONCE(bo->bound)) return -ENOTCONN; /* check for valid message length from userspace */ @@ -1385,17 +1417,38 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) if ((size - MHSIZ) % cfsiz) return -EINVAL; + lock_sock(sk); + + /* Re-validate under the socket lock: a concurrent bcm_notify() + * may have unbound this socket (device removal) after the + * lockless fast-path check above. bo->ifindex is only ever + * mutated under lock_sock(), so reading it here - instead of + * before taking the lock - guarantees it can't be observed + * torn against bo->bound. + */ + if (!bo->bound) { + ret = -ENOTCONN; + goto out_release; + } + + /* default ifindex for this bcm_op */ + ifindex = bo->ifindex; + /* check for alternative ifindex for this bcm_op */ if (!ifindex && msg->msg_name) { /* no bound device as default => check msg_name */ DECLARE_SOCKADDR(struct sockaddr_can *, addr, msg->msg_name); - if (msg->msg_namelen < BCM_MIN_NAMELEN) - return -EINVAL; + if (msg->msg_namelen < BCM_MIN_NAMELEN) { + ret = -EINVAL; + goto out_release; + } - if (addr->can_family != AF_CAN) - return -EINVAL; + if (addr->can_family != AF_CAN) { + ret = -EINVAL; + goto out_release; + } /* ifindex from sendto() */ ifindex = addr->can_ifindex; @@ -1404,20 +1457,21 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) struct net_device *dev; dev = dev_get_by_index(sock_net(sk), ifindex); - if (!dev) - return -ENODEV; + if (!dev) { + ret = -ENODEV; + goto out_release; + } if (dev->type != ARPHRD_CAN) { dev_put(dev); - return -ENODEV; + ret = -ENODEV; + goto out_release; } dev_put(dev); } } - lock_sock(sk); - switch (msg_head.opcode) { case TX_SETUP: @@ -1467,6 +1521,7 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) break; } +out_release: release_sock(sk); return ret; @@ -1503,7 +1558,12 @@ static void bcm_notify(struct bcm_sock *bo, unsigned long msg, bo->bcm_proc_read = NULL; } #endif - bo->bound = 0; + /* Paired with the lockless fast-path check in + * bcm_sendmsg(); bo->ifindex itself is only ever + * accessed under lock_sock() so it needs no + * annotation. + */ + WRITE_ONCE(bo->bound, 0); bo->ifindex = 0; notify_enodev = 1; } @@ -1607,8 +1667,10 @@ static int bcm_release(struct socket *sock) remove_proc_entry(bo->procname, net->can.bcmproc_dir); #endif /* CONFIG_PROC_FS */ - list_for_each_entry_safe(op, next, &bo->tx_ops, list) + list_for_each_entry_safe(op, next, &bo->tx_ops, list) { + list_del_rcu(&op->list); bcm_remove_op(op); + } list_for_each_entry_safe(op, next, &bo->rx_ops, list) { /* @@ -1639,12 +1701,14 @@ static int bcm_release(struct socket *sock) synchronize_rcu(); - list_for_each_entry_safe(op, next, &bo->rx_ops, list) + list_for_each_entry_safe(op, next, &bo->rx_ops, list) { + list_del_rcu(&op->list); bcm_remove_op(op); + } /* remove device reference */ if (bo->bound) { - bo->bound = 0; + WRITE_ONCE(bo->bound, 0); bo->ifindex = 0; } @@ -1714,7 +1778,10 @@ static int bcm_connect(struct socket *sock, struct sockaddr *uaddr, int len, } #endif /* CONFIG_PROC_FS */ - bo->bound = 1; + /* bo->ifindex above is fully assigned before this point; pairs + * with the lockless fast-path check in bcm_sendmsg() + */ + WRITE_ONCE(bo->bound, 1); fail: release_sock(sk); @@ -1830,11 +1897,15 @@ static int __init bcm_module_init(void) { int err; + bcm_wq = alloc_workqueue("can-bcm-wq", WQ_UNBOUND, 0); + if (!bcm_wq) + return -ENOMEM; + pr_info("can: broadcast manager protocol\n"); err = register_pernet_subsys(&canbcm_pernet_ops); if (err) - return err; + goto register_pernet_failed; err = register_netdevice_notifier(&canbcm_notifier); if (err) @@ -1852,6 +1923,8 @@ register_proto_failed: unregister_netdevice_notifier(&canbcm_notifier); register_notifier_failed: unregister_pernet_subsys(&canbcm_pernet_ops); +register_pernet_failed: + destroy_workqueue(bcm_wq); return err; } @@ -1860,6 +1933,8 @@ static void __exit bcm_module_exit(void) can_proto_unregister(&bcm_can_proto); unregister_netdevice_notifier(&canbcm_notifier); unregister_pernet_subsys(&canbcm_pernet_ops); + rcu_barrier(); + destroy_workqueue(bcm_wq); } module_init(bcm_module_init); diff --git a/net/can/isotp.c b/net/can/isotp.c index 12eabfd47d82..c21d816a5274 100644 --- a/net/can/isotp.c +++ b/net/can/isotp.c @@ -151,11 +151,13 @@ struct isotp_sock { struct sock sk; int bound; int ifindex; + struct net_device *dev; + netdevice_tracker dev_tracker; canid_t txid; canid_t rxid; ktime_t tx_gap; ktime_t lastrxcf_tstamp; - struct hrtimer rxtimer, txtimer, txfrtimer; + struct hrtimer rxtimer, txtimer, txfrtimer, echotimer; struct can_isotp_options opt; struct can_isotp_fc_options rxfc, txfc; struct can_isotp_ll_options ll; @@ -163,6 +165,7 @@ struct isotp_sock { u32 force_tx_stmin; u32 force_rx_stmin; u32 cfecho; /* consecutive frame echo tag */ + u32 tx_gen; /* generation, bumped per new tx transfer */ struct tpcon rx, tx; struct list_head notifier; wait_queue_head_t wait; @@ -370,6 +373,15 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) hrtimer_cancel(&so->txtimer); + /* isotp_tx_timeout() may have given up on this job while + * hrtimer_cancel() above waited for it to finish; so->rx_lock + * (held by our caller isotp_rcv()) rules out a concurrent claim, + * so a plain recheck is enough here. + */ + if (so->tx.state != ISOTP_WAIT_FC && + so->tx.state != ISOTP_WAIT_FIRST_FC) + return 1; + if ((cf->len < ae + FC_CONTENT_SZ) || ((so->opt.flags & ISOTP_CHECK_PADDING) && check_pad(so, cf, ae + FC_CONTENT_SZ, so->opt.rxpad_content))) { @@ -416,7 +428,7 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct canfd_frame *cf, int ae) so->tx.bs = 0; so->tx.state = ISOTP_SENDING; /* send CF frame and enable echo timeout handling */ - hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), HRTIMER_MODE_REL_SOFT); isotp_send_cframe(so); break; @@ -569,6 +581,14 @@ static int isotp_rcv_cf(struct sock *sk, struct canfd_frame *cf, int ae, hrtimer_cancel(&so->rxtimer); + /* isotp_rx_timer_handler() may have raced us for so->rx.state + * while hrtimer_cancel() above waited for it to finish, already + * reporting ETIMEDOUT and resetting the reception; don't process + * this CF into a reassembly that has already been given up on. + */ + if (so->rx.state != ISOTP_WAIT_DATA) + return 1; + /* CFs are never longer than the FF */ if (cf->len > so->rx.ll_dl) return 1; @@ -858,20 +878,36 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) struct canfd_frame *cf = (struct canfd_frame *)skb->data; /* only handle my own local echo CF/SF skb's (no FF!) */ - if (skb->sk != sk || so->cfecho != *(u32 *)cf->data) + if (skb->sk != sk) return; + /* unlike isotp_rcv_fc()/isotp_rcv_cf(), not already under so->rx_lock + * (no isotp_rcv() caller here), so take it ourselves + */ + spin_lock(&so->rx_lock); + + /* so->cfecho may since belong to a new transfer; recheck under lock */ + if (so->cfecho != *(u32 *)cf->data) + goto out_unlock; + /* cancel local echo timeout */ - hrtimer_cancel(&so->txtimer); + hrtimer_cancel(&so->echotimer); /* local echo skb with consecutive frame has been consumed */ so->cfecho = 0; + /* claiming a transfer also takes so->rx_lock, so a plain recheck + * is enough: so->tx.state can't have flipped to ISOTP_SENDING for + * a new claim while we're still in here + */ + if (so->tx.state != ISOTP_SENDING) + goto out_unlock; + if (so->tx.idx >= so->tx.len) { /* we are done */ so->tx.state = ISOTP_IDLE; wake_up_interruptible(&so->wait); - return; + goto out_unlock; } if (so->txfc.bs && so->tx.bs >= so->txfc.bs) { @@ -879,53 +915,83 @@ static void isotp_rcv_echo(struct sk_buff *skb, void *data) so->tx.state = ISOTP_WAIT_FC; hrtimer_start(&so->txtimer, ktime_set(ISOTP_FC_TIMEOUT, 0), HRTIMER_MODE_REL_SOFT); - return; + goto out_unlock; } /* no gap between data frames needed => use burst mode */ if (!so->tx_gap) { /* enable echo timeout handling */ - hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), HRTIMER_MODE_REL_SOFT); isotp_send_cframe(so); - return; + goto out_unlock; } /* start timer to send next consecutive frame with correct delay */ hrtimer_start(&so->txfrtimer, so->tx_gap, HRTIMER_MODE_REL_SOFT); + +out_unlock: + spin_unlock(&so->rx_lock); } -static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) +/* shared by so->txtimer's and so->echotimer's callbacks. Both timers get + * cancelled under so->rx_lock elsewhere, so this must stay lock-free to + * avoid deadlocking with that; uses so->tx_gen instead to avoid tainting + * a new transfer with an error from the one that just timed out. + */ +static enum hrtimer_restart isotp_tx_timeout(struct isotp_sock *so) { - struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, - txtimer); struct sock *sk = &so->sk; + u32 gen = READ_ONCE(so->tx_gen); + u32 old_state = READ_ONCE(so->tx.state); /* don't handle timeouts in IDLE or SHUTDOWN state */ - if (so->tx.state == ISOTP_IDLE || so->tx.state == ISOTP_SHUTDOWN) + if (old_state == ISOTP_IDLE || old_state == ISOTP_SHUTDOWN) + return HRTIMER_NORESTART; + + /* only claim the timeout if the state is still unchanged */ + if (cmpxchg(&so->tx.state, old_state, ISOTP_IDLE) != old_state) return HRTIMER_NORESTART; /* we did not get any flow control or echo frame in time */ - /* report 'communication error on send' */ - sk->sk_err = ECOMM; - if (!sock_flag(sk, SOCK_DEAD)) - sk_error_report(sk); + if (READ_ONCE(so->tx_gen) == gen) { + /* report 'communication error on send' */ + sk->sk_err = ECOMM; + if (!sock_flag(sk, SOCK_DEAD)) + sk_error_report(sk); + } - /* reset tx state */ - so->tx.state = ISOTP_IDLE; wake_up_interruptible(&so->wait); return HRTIMER_NORESTART; } +/* so->txtimer: fires when a Flow Control frame does not arrive in time */ +static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer) +{ + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, + txtimer); + + return isotp_tx_timeout(so); +} + +/* so->echotimer: fires when a sent CF/SF's local echo does not arrive */ +static enum hrtimer_restart isotp_echo_timer_handler(struct hrtimer *hrtimer) +{ + struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, + echotimer); + + return isotp_tx_timeout(so); +} + static enum hrtimer_restart isotp_txfr_timer_handler(struct hrtimer *hrtimer) { struct isotp_sock *so = container_of(hrtimer, struct isotp_sock, txfrtimer); /* start echo timeout handling and cover below protocol error */ - hrtimer_start(&so->txtimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), + hrtimer_start(&so->echotimer, ktime_set(ISOTP_ECHO_TIMEOUT, 0), HRTIMER_MODE_REL_SOFT); /* cfecho should be consumed by isotp_rcv_echo() here */ @@ -945,13 +1011,24 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR) ? 1 : 0; int wait_tx_done = (so->opt.flags & CAN_ISOTP_WAIT_TX_DONE) ? 1 : 0; s64 hrtimer_sec = ISOTP_ECHO_TIMEOUT; + struct hrtimer *tx_hrt = &so->echotimer; + u32 new_state = ISOTP_SENDING; int off; int err; if (!so->bound || so->tx.state == ISOTP_SHUTDOWN) return -EADDRNOTAVAIL; - while (cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SENDING) != ISOTP_IDLE) { + /* claim the socket under so->rx_lock: this serializes the claim + * with the RX path and with sendmsg()'s own error paths below, so + * none of them can ever see a transfer mid-claim + */ + for (;;) { + spin_lock_bh(&so->rx_lock); + if (READ_ONCE(so->tx.state) == ISOTP_IDLE) + break; + spin_unlock_bh(&so->rx_lock); + /* we do not support multiple buffers - for now */ if (msg->msg_flags & MSG_DONTWAIT) return -EAGAIN; @@ -960,9 +1037,29 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) return -EADDRNOTAVAIL; /* wait for complete transmission of current pdu */ - err = wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE); + err = wait_event_interruptible(so->wait, + so->tx.state == ISOTP_IDLE); if (err) - goto err_event_drop; + return err; + } + + /* new transfer: bump so->tx_gen and drain the old one's timers, + * still under the so->rx_lock we just claimed the socket with + */ + WRITE_ONCE(so->tx.state, ISOTP_SENDING); + WRITE_ONCE(so->tx_gen, READ_ONCE(so->tx_gen) + 1); + hrtimer_cancel(&so->txtimer); + hrtimer_cancel(&so->echotimer); + hrtimer_cancel(&so->txfrtimer); + so->cfecho = 0; + spin_unlock_bh(&so->rx_lock); + + /* so->bound is only checked once above - a wakeup may have + * unbound/rebound the socket meanwhile, so re-validate it + */ + if (!so->bound) { + err = -EADDRNOTAVAIL; + goto err_out_drop; } /* PDU size > default => try max_pdu_size */ @@ -1067,18 +1164,33 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) so->cfecho = *(u32 *)cf->data; } else { /* standard flow control check */ - so->tx.state = ISOTP_WAIT_FIRST_FC; + new_state = ISOTP_WAIT_FIRST_FC; /* start timeout for FC */ hrtimer_sec = ISOTP_FC_TIMEOUT; + tx_hrt = &so->txtimer; /* no CF echo tag for isotp_rcv_echo() (FF-mode) */ so->cfecho = 0; } } - hrtimer_start(&so->txtimer, ktime_set(hrtimer_sec, 0), + spin_lock_bh(&so->rx_lock); + if (so->tx.state == ISOTP_SHUTDOWN) { + /* isotp_release() has since taken over and already drained + * our timers - don't send into a socket that's going away + */ + spin_unlock_bh(&so->rx_lock); + kfree_skb(skb); + dev_put(dev); + wake_up_interruptible(&so->wait); + return -EADDRNOTAVAIL; + } + /* WAIT_FIRST_FC for standard FF, else stays ISOTP_SENDING */ + so->tx.state = new_state; + hrtimer_start(tx_hrt, ktime_set(hrtimer_sec, 0), HRTIMER_MODE_REL_SOFT); + spin_unlock_bh(&so->rx_lock); /* send the first or only CAN frame */ cf->flags = so->ll.tx_flags; @@ -1091,13 +1203,10 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) pr_notice_once("can-isotp: %s: can_send_ret %pe\n", __func__, ERR_PTR(err)); + spin_lock_bh(&so->rx_lock); /* no transmission -> no timeout monitoring */ - hrtimer_cancel(&so->txtimer); - - /* reset consecutive frame echo tag */ - so->cfecho = 0; - - goto err_out_drop; + hrtimer_cancel(tx_hrt); + goto err_out_drop_locked; } if (wait_tx_done) { @@ -1113,14 +1222,21 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) return size; +err_out_drop: + /* claimed but nothing sent yet - no timer to cancel */ + spin_lock_bh(&so->rx_lock); + goto err_out_drop_locked; err_event_drop: - /* got signal: force tx state machine to be idle */ - so->tx.state = ISOTP_IDLE; + /* interrupted waiting on our own transfer - drain its timers */ + spin_lock_bh(&so->rx_lock); hrtimer_cancel(&so->txfrtimer); hrtimer_cancel(&so->txtimer); -err_out_drop: - /* drop this PDU and unlock a potential wait queue */ + hrtimer_cancel(&so->echotimer); +err_out_drop_locked: + /* release the claim; so->rx_lock still held from above */ + so->cfecho = 0; so->tx.state = ISOTP_IDLE; + spin_unlock_bh(&so->rx_lock); wake_up_interruptible(&so->wait); return err; @@ -1182,13 +1298,20 @@ static int isotp_release(struct socket *sock) so = isotp_sk(sk); net = sock_net(sk); - /* wait for complete transmission of current pdu */ - while (wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0 && - cmpxchg(&so->tx.state, ISOTP_IDLE, ISOTP_SHUTDOWN) != ISOTP_IDLE) + /* best-effort: wait for a running pdu to finish, but don't block on + * it forever - give up after the first signal + */ + while (so->tx.state != ISOTP_IDLE && + wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE) == 0) ; - /* force state machines to be idle also when a signal occurred */ + /* claim the socket under so->rx_lock like sendmsg() does, so its + * claim can't race the forced ISOTP_SHUTDOWN below; force it + * unconditionally, even when a signal cut the wait above short + */ + spin_lock_bh(&so->rx_lock); so->tx.state = ISOTP_SHUTDOWN; + spin_unlock_bh(&so->rx_lock); so->rx.state = ISOTP_IDLE; spin_lock(&isotp_notifier_lock); @@ -1200,36 +1323,43 @@ static int isotp_release(struct socket *sock) list_del(&so->notifier); spin_unlock(&isotp_notifier_lock); + rtnl_lock(); lock_sock(sk); - /* remove current filters & unregister */ - if (so->bound) { - if (so->ifindex) { - struct net_device *dev; - - dev = dev_get_by_index(net, so->ifindex); - if (dev) { - if (isotp_register_rxid(so)) - can_rx_unregister(net, dev, so->rxid, - SINGLE_MASK(so->rxid), - isotp_rcv, sk); - - can_rx_unregister(net, dev, so->txid, - SINGLE_MASK(so->txid), - isotp_rcv_echo, sk); - dev_put(dev); - synchronize_rcu(); - } - } + /* remove current filters & unregister + * tracked reference so->dev is taken at bind() time with rtnl_lock + */ + if (so->bound && so->dev) { + if (isotp_register_rxid(so)) + can_rx_unregister(net, so->dev, so->rxid, + SINGLE_MASK(so->rxid), + isotp_rcv, sk); + + can_rx_unregister(net, so->dev, so->txid, + SINGLE_MASK(so->txid), + isotp_rcv_echo, sk); + netdev_put(so->dev, &so->dev_tracker); } + so->ifindex = 0; + so->bound = 0; + so->dev = NULL; + + rtnl_unlock(); + + /* Always wait for a grace period before touching the timers below. + * A concurrent NETDEV_UNREGISTER may have already unregistered our + * filters and cleared so->bound in isotp_notify() without waiting + * for in-flight isotp_rcv() callers to finish, so this call must not + * be skipped just because so->bound is already 0 here. + */ + synchronize_rcu(); + hrtimer_cancel(&so->txfrtimer); hrtimer_cancel(&so->txtimer); + hrtimer_cancel(&so->echotimer); hrtimer_cancel(&so->rxtimer); - so->ifindex = 0; - so->bound = 0; - sock_orphan(sk); sock->sk = NULL; @@ -1284,6 +1414,7 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) if (!addr->can_ifindex) return -ENODEV; + rtnl_lock(); lock_sock(sk); if (so->bound) { @@ -1291,6 +1422,17 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) goto out; } + /* A transmission or reception that outlived a previous binding + * (unbound by NETDEV_UNREGISTER) may still be draining; the FC/echo + * and RX watchdog timers bound how long this takes. Checked together + * with so->bound in the same lock_sock() section above, so there is + * no window in which a concurrent isotp_notify() could be missed. + */ + if (so->tx.state != ISOTP_IDLE || so->rx.state != ISOTP_IDLE) { + err = -EAGAIN; + goto out; + } + /* ensure different CAN IDs when the rx_id is to be registered */ if (isotp_register_rxid(so) && rx_id == tx_id) { err = -EADDRNOTAVAIL; @@ -1303,14 +1445,12 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) goto out; } if (dev->type != ARPHRD_CAN) { - dev_put(dev); err = -ENODEV; - goto out; + goto out_put_dev; } if (READ_ONCE(dev->mtu) < so->ll.mtu) { - dev_put(dev); err = -EINVAL; - goto out; + goto out_put_dev; } if (!(dev->flags & IFF_UP)) notify_enetdown = 1; @@ -1328,16 +1468,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len) can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id), isotp_rcv_echo, sk, "isotpe", sk); - dev_put(dev); - /* switch to new settings */ so->ifindex = ifindex; so->rxid = rx_id; so->txid = tx_id; so->bound = 1; + /* bind() ok -> hold a reference for so->dev so that isotp_release() + * can safely reach the device later, even if a concurrent + * NETDEV_UNREGISTER has already unlisted it by ifindex. + */ + so->dev = dev; + netdev_hold(so->dev, &so->dev_tracker, GFP_KERNEL); + +out_put_dev: + /* remove potential reference from dev_get_by_index() */ + dev_put(dev); out: release_sock(sk); + rtnl_unlock(); if (notify_enetdown) { sk->sk_err = ENETDOWN; @@ -1540,7 +1689,7 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, if (!net_eq(dev_net(dev), sock_net(sk))) return; - if (so->ifindex != dev->ifindex) + if (so->dev != dev) return; switch (msg) { @@ -1556,10 +1705,12 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg, can_rx_unregister(dev_net(dev), dev, so->txid, SINGLE_MASK(so->txid), isotp_rcv_echo, sk); + netdev_put(so->dev, &so->dev_tracker); } so->ifindex = 0; so->bound = 0; + so->dev = NULL; release_sock(sk); sk->sk_err = ENODEV; @@ -1619,6 +1770,7 @@ static int isotp_init(struct sock *sk) so->ifindex = 0; so->bound = 0; + so->dev = NULL; so->opt.flags = CAN_ISOTP_DEFAULT_FLAGS; so->opt.ext_address = CAN_ISOTP_DEFAULT_EXT_ADDRESS; @@ -1645,10 +1797,14 @@ static int isotp_init(struct sock *sk) so->rx.buflen = ARRAY_SIZE(so->rx.sbuf); so->tx.buflen = ARRAY_SIZE(so->tx.sbuf); - hrtimer_setup(&so->rxtimer, isotp_rx_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); - hrtimer_setup(&so->txtimer, isotp_tx_timer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); - hrtimer_setup(&so->txfrtimer, isotp_txfr_timer_handler, CLOCK_MONOTONIC, - HRTIMER_MODE_REL_SOFT); + hrtimer_setup(&so->rxtimer, isotp_rx_timer_handler, + CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + hrtimer_setup(&so->txtimer, isotp_tx_timer_handler, + CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + hrtimer_setup(&so->echotimer, isotp_echo_timer_handler, + CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + hrtimer_setup(&so->txfrtimer, isotp_txfr_timer_handler, + CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); init_waitqueue_head(&so->wait); spin_lock_init(&so->rx_lock); diff --git a/net/core/dev.c b/net/core/dev.c index 5853aada258c..8dee703a0916 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -11138,7 +11138,8 @@ static int netif_alloc_netdev_queues(struct net_device *dev) netdev_for_each_tx_queue(dev, netdev_init_one_queue, NULL); spin_lock_init(&dev->tx_global_lock); - + spin_lock_init(&dev->watchdog_lock); + dev->watchdog_ref_held = false; return 0; } diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index 8ca634964e36..cf374c208732 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -349,7 +349,7 @@ jumped: if (err != -EAGAIN) { if ((arg->flags & FIB_LOOKUP_NOREF) || - likely(refcount_inc_not_zero(&rule->refcnt))) { + likely(fib_rule_get_safe(rule))) { arg->rule = rule; goto out; } @@ -410,8 +410,12 @@ int fib_rules_dump(struct net *net, struct notifier_block *nb, int family, if (!ops) return -EAFNOSUPPORT; list_for_each_entry_rcu(rule, &ops->rules_list, list) { + if (!fib_rule_get_safe(rule)) + continue; + err = call_fib_rule_notifier(nb, FIB_EVENT_RULE_ADD, rule, family, extack); + fib_rule_put(rule); if (err) break; } diff --git a/net/core/filter.c b/net/core/filter.c index 6dd9bdbef199..42385e790431 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -2833,6 +2833,9 @@ BPF_CALL_4(bpf_msg_push_data, struct sk_msg *, msg, u32, start, if (!space || (space == 1 && start != offset)) copy = msg->sg.data[i].length; + if (unlikely(copy + len < copy)) + return -EINVAL; + page = alloc_pages(__GFP_NOWARN | GFP_ATOMIC | __GFP_COMP, get_order(copy + len)); if (unlikely(!page)) @@ -2996,8 +2999,8 @@ BPF_CALL_4(bpf_msg_pop_data, struct sk_msg *, msg, u32, start, u32, len, u64, flags) { u32 i = 0, l = 0, space, offset = 0; - u64 last = start + len; - int pop; + u64 last = (u64)start + len; + u32 pop; if (unlikely(flags)) return -EINVAL; @@ -4421,7 +4424,7 @@ u32 xdp_master_redirect(struct xdp_buff *xdp) struct net_device *master, *slave; master = netdev_master_upper_dev_get_rcu(xdp->rxq->dev); - if (unlikely(!(master->flags & IFF_UP))) + if (unlikely(!master || !(master->flags & IFF_UP))) return XDP_ABORTED; slave = master->netdev_ops->ndo_xdp_get_xmit_slave(master, xdp); if (slave && slave != xdp->rxq->dev) { @@ -5562,11 +5565,24 @@ static int sol_tcp_sockopt(struct sock *sk, int optname, KERNEL_SOCKPTR(optval), *optlen); } +static bool sk_allows_sol_ip_sockopt(struct sock *sk) +{ + switch (sk->sk_family) { + case AF_INET: + return true; + case AF_INET6: + /* Allow getting/setting sockopt for possible ipv4-mapped ipv6 socket. */ + return sk->sk_type != SOCK_RAW && !ipv6_only_sock(sk); + default: + return false; + } +} + static int sol_ip_sockopt(struct sock *sk, int optname, char *optval, int *optlen, bool getopt) { - if (sk->sk_family != AF_INET) + if (!sk_allows_sol_ip_sockopt(sk)) return -EINVAL; switch (optname) { @@ -6060,7 +6076,7 @@ static int bpf_ipv4_fib_lookup(struct net *net, struct bpf_fib_lookup *params, struct neighbour *neigh; struct net_device *dev; struct fib_result res; - struct flowi4 fl4; + struct flowi4 fl4 = {}; u32 mtu = 0; int err; @@ -6200,7 +6216,7 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params, struct neighbour *neigh; struct net_device *dev; struct inet6_dev *idev; - struct flowi6 fl6; + struct flowi6 fl6 = {}; int strict = 0; int oif, err; u32 mtu = 0; diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c index 2a98f5fa74eb..8aa4f9b4df81 100644 --- a/net/core/flow_dissector.c +++ b/net/core/flow_dissector.c @@ -1173,13 +1173,21 @@ bool __skb_flow_dissect(const struct net *net, if (dissector_uses_key(flow_dissector, FLOW_DISSECTOR_KEY_ETH_ADDRS)) { - struct ethhdr *eth = eth_hdr(skb); struct flow_dissector_key_eth_addrs *key_eth_addrs; key_eth_addrs = skb_flow_dissector_target(flow_dissector, FLOW_DISSECTOR_KEY_ETH_ADDRS, target_container); - memcpy(key_eth_addrs, eth, sizeof(*key_eth_addrs)); + /* TC filter blocks can be shared across devices with + * different link types, so we cannot validate this + * when the filter is installed -- check at dissect time. + */ + if (skb && skb->dev && + skb->dev->type == ARPHRD_ETHER && + skb_mac_header_was_set(skb)) + memcpy(key_eth_addrs, eth_hdr(skb), sizeof(*key_eth_addrs)); + else + memset(key_eth_addrs, 0, sizeof(*key_eth_addrs)); } if (dissector_uses_key(flow_dissector, diff --git a/net/core/lwt_bpf.c b/net/core/lwt_bpf.c index 9f40be0c3e71..2efcda93d139 100644 --- a/net/core/lwt_bpf.c +++ b/net/core/lwt_bpf.c @@ -595,6 +595,7 @@ static int handle_gso_encap(struct sk_buff *skb, bool ipv4, int encap_len) int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) { + bool is_udp_tunnel; struct iphdr *iph; bool ipv4; int err; @@ -608,10 +609,16 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) ipv4 = true; if (unlikely(len < iph->ihl * 4)) return -EINVAL; + is_udp_tunnel = iph->protocol == IPPROTO_UDP; + if (unlikely(is_udp_tunnel && len < iph->ihl * 4 + sizeof(struct udphdr))) + return -EINVAL; } else if (iph->version == 6) { ipv4 = false; if (unlikely(len < sizeof(struct ipv6hdr))) return -EINVAL; + is_udp_tunnel = ((struct ipv6hdr *)iph)->nexthdr == NEXTHDR_UDP; + if (unlikely(is_udp_tunnel && len < sizeof(struct ipv6hdr) + sizeof(struct udphdr))) + return -EINVAL; } else { return -EINVAL; } @@ -633,6 +640,11 @@ int bpf_lwt_push_ip_encap(struct sk_buff *skb, void *hdr, u32 len, bool ingress) if (ingress) skb_postpush_rcsum(skb, iph, len); skb_reset_network_header(skb); + if (is_udp_tunnel) { + size_t iph_sz = ipv4 ? iph->ihl * 4 : sizeof(struct ipv6hdr); + + skb_set_transport_header(skb, skb_network_offset(skb) + iph_sz); + } memcpy(skb_network_header(skb), hdr, len); bpf_compute_data_pointers(skb); skb_clear_hash(skb); diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index 93ea09bd1e7b..ba35ba5ae784 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -2,6 +2,7 @@ #include <linux/netdevice.h> #include <linux/notifier.h> +#include <linux/pid_namespace.h> #include <linux/rtnetlink.h> #include <net/busy_poll.h> #include <net/net_namespace.h> @@ -189,7 +190,8 @@ netdev_nl_napi_fill_one(struct sk_buff *rsp, struct napi_struct *napi, goto nla_put_failure; if (napi->thread) { - pid = task_pid_nr(napi->thread); + pid = task_pid_nr_ns(napi->thread, + task_active_pid_ns(current)); if (nla_put_u32(rsp, NETDEV_A_NAPI_PID, pid)) goto nla_put_failure; } diff --git a/net/core/sock.c b/net/core/sock.c index 04fa0c18adc3..c52cec0bf942 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -3900,13 +3900,8 @@ int sock_common_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, int flags) { struct sock *sk = sock->sk; - int addr_len = 0; - int err; - err = sk->sk_prot->recvmsg(sk, msg, size, flags, &addr_len); - if (err >= 0) - msg->msg_namelen = addr_len; - return err; + return sk->sk_prot->recvmsg(sk, msg, size, flags); } EXPORT_SYMBOL(sock_common_recvmsg); diff --git a/net/devlink/rate.c b/net/devlink/rate.c index d157a8419bca..de13b9818dfc 100644 --- a/net/devlink/rate.c +++ b/net/devlink/rate.c @@ -487,16 +487,19 @@ static int devlink_nl_rate_set(struct devlink_rate *devlink_rate, devlink_rate->tx_weight = weight; } - nla_parent = attrs[DEVLINK_ATTR_RATE_PARENT_NODE_NAME]; - if (nla_parent) { - err = devlink_nl_rate_parent_node_set(devlink_rate, info, - nla_parent); + if (attrs[DEVLINK_ATTR_RATE_TC_BWS]) { + err = devlink_nl_rate_tc_bw_set(devlink_rate, info); if (err) return err; } - if (attrs[DEVLINK_ATTR_RATE_TC_BWS]) { - err = devlink_nl_rate_tc_bw_set(devlink_rate, info); + /* Keep parent setting last because it takes a reference. This function + * has no rollback, so failing after taking the ref would leak it. + */ + nla_parent = attrs[DEVLINK_ATTR_RATE_PARENT_NODE_NAME]; + if (nla_parent) { + err = devlink_nl_rate_parent_node_set(devlink_rate, info, + nla_parent); if (err) return err; } @@ -724,11 +727,6 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, if (!rate_node) return ERR_PTR(-ENOMEM); - if (parent) { - rate_node->parent = parent; - refcount_inc(&rate_node->parent->refcnt); - } - rate_node->type = DEVLINK_RATE_TYPE_NODE; rate_node->devlink = devlink; rate_node->priv = priv; @@ -739,6 +737,11 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, return ERR_PTR(-ENOMEM); } + if (parent) { + rate_node->parent = parent; + refcount_inc(&rate_node->parent->refcnt); + } + refcount_set(&rate_node->refcnt, 1); list_add(&rate_node->list, &devlink->rate_list); devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); diff --git a/net/handshake/genl.c b/net/handshake/genl.c index a5fa8b27f224..7ec805ae3d7d 100644 --- a/net/handshake/genl.c +++ b/net/handshake/genl.c @@ -37,7 +37,7 @@ static const struct genl_split_ops handshake_nl_ops[] = { .doit = handshake_nl_done_doit, .policy = handshake_done_nl_policy, .maxattr = HANDSHAKE_A_DONE_REMOTE_AUTH, - .flags = GENL_CMD_CAP_DO, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, }; diff --git a/net/ieee802154/core.c b/net/ieee802154/core.c index 89b671b12600..c0b8712018a1 100644 --- a/net/ieee802154/core.c +++ b/net/ieee802154/core.c @@ -228,36 +228,43 @@ int cfg802154_switch_netns(struct cfg802154_registered_device *rdev, continue; wpan_dev->netdev->netns_immutable = false; err = dev_change_net_namespace(wpan_dev->netdev, net, "wpan%d"); - if (err) + if (err) { + WARN_ON(err && err != -ENOMEM); break; + } wpan_dev->netdev->netns_immutable = true; } - if (err) { - /* failed -- clean up to old netns */ - net = wpan_phy_net(&rdev->wpan_phy); - - list_for_each_entry_continue_reverse(wpan_dev, - &rdev->wpan_dev_list, - list) { - if (!wpan_dev->netdev) - continue; - wpan_dev->netdev->netns_immutable = false; - err = dev_change_net_namespace(wpan_dev->netdev, net, - "wpan%d"); - WARN_ON(err); - wpan_dev->netdev->netns_immutable = true; - } + if (err) + goto errout; - return err; - } + err = device_rename(&rdev->wpan_phy.dev, dev_name(&rdev->wpan_phy.dev)); + WARN_ON(err && err != -ENOMEM); - wpan_phy_net_set(&rdev->wpan_phy, net); + if (err) + goto errout; - err = device_rename(&rdev->wpan_phy.dev, dev_name(&rdev->wpan_phy.dev)); - WARN_ON(err); + wpan_phy_net_set(&rdev->wpan_phy, net); return 0; + +errout: + /* failed -- clean up to old netns */ + net = wpan_phy_net(&rdev->wpan_phy); + + list_for_each_entry_continue_reverse(wpan_dev, + &rdev->wpan_dev_list, + list) { + if (!wpan_dev->netdev) + continue; + wpan_dev->netdev->netns_immutable = false; + err = dev_change_net_namespace(wpan_dev->netdev, net, + "wpan%d"); + WARN_ON(err && err != -ENOMEM); + wpan_dev->netdev->netns_immutable = true; + } + + return err; } void cfg802154_dev_free(struct cfg802154_registered_device *rdev) @@ -351,7 +358,7 @@ static void __net_exit cfg802154_pernet_exit(struct net *net) rtnl_lock(); list_for_each_entry(rdev, &cfg802154_rdev_list, list) { if (net_eq(wpan_phy_net(&rdev->wpan_phy), net)) - WARN_ON(cfg802154_switch_netns(rdev, &init_net)); + cfg802154_switch_netns(rdev, &init_net); } rtnl_unlock(); } diff --git a/net/ieee802154/header_ops.c b/net/ieee802154/header_ops.c index 41a556be1017..a9f0c8df5ae4 100644 --- a/net/ieee802154/header_ops.c +++ b/net/ieee802154/header_ops.c @@ -173,10 +173,13 @@ ieee802154_hdr_get_addr(const u8 *buf, int mode, bool omit_pan, { int pos = 0; - addr->mode = mode; - - if (mode == IEEE802154_ADDR_NONE) + if (mode == IEEE802154_ADDR_NONE) { + memset(addr, 0, sizeof(*addr)); + addr->mode = IEEE802154_ADDR_NONE; return 0; + } + + addr->mode = mode; if (!omit_pan) { memcpy(&addr->pan_id, buf + pos, 2); diff --git a/net/ieee802154/ieee802154.h b/net/ieee802154/ieee802154.h index c5d91f78301a..e765adc4b88f 100644 --- a/net/ieee802154/ieee802154.h +++ b/net/ieee802154/ieee802154.h @@ -16,6 +16,15 @@ void ieee802154_nl_exit(void); .flags = GENL_ADMIN_PERM, \ } +#define IEEE802154_OP_RELAXED(_cmd, _func) \ + { \ + .cmd = _cmd, \ + .doit = _func, \ + .dumpit = NULL, \ + .flags = GENL_ADMIN_PERM, \ + .validate = GENL_DONT_VALIDATE_STRICT,\ + } + #define IEEE802154_DUMP(_cmd, _func, _dump) \ { \ .cmd = _cmd, \ @@ -23,6 +32,14 @@ void ieee802154_nl_exit(void); .dumpit = _dump, \ } +#define IEEE802154_DUMP_PRIV(_cmd, _func, _dump) \ + { \ + .cmd = _cmd, \ + .doit = _func, \ + .dumpit = _dump, \ + .flags = GENL_ADMIN_PERM, \ + } + struct genl_info; struct sk_buff *ieee802154_nl_create(int flags, u8 req); diff --git a/net/ieee802154/netlink.c b/net/ieee802154/netlink.c index 7d2de4ee6992..cacad21347ec 100644 --- a/net/ieee802154/netlink.c +++ b/net/ieee802154/netlink.c @@ -98,24 +98,24 @@ static const struct genl_small_ops ieee802154_ops[] = { IEEE802154_OP(IEEE802154_SET_MACPARAMS, ieee802154_set_macparams), IEEE802154_OP(IEEE802154_LLSEC_GETPARAMS, ieee802154_llsec_getparams), IEEE802154_OP(IEEE802154_LLSEC_SETPARAMS, ieee802154_llsec_setparams), - IEEE802154_DUMP(IEEE802154_LLSEC_LIST_KEY, NULL, - ieee802154_llsec_dump_keys), - IEEE802154_OP(IEEE802154_LLSEC_ADD_KEY, ieee802154_llsec_add_key), - IEEE802154_OP(IEEE802154_LLSEC_DEL_KEY, ieee802154_llsec_del_key), - IEEE802154_DUMP(IEEE802154_LLSEC_LIST_DEV, NULL, - ieee802154_llsec_dump_devs), - IEEE802154_OP(IEEE802154_LLSEC_ADD_DEV, ieee802154_llsec_add_dev), - IEEE802154_OP(IEEE802154_LLSEC_DEL_DEV, ieee802154_llsec_del_dev), - IEEE802154_DUMP(IEEE802154_LLSEC_LIST_DEVKEY, NULL, - ieee802154_llsec_dump_devkeys), - IEEE802154_OP(IEEE802154_LLSEC_ADD_DEVKEY, ieee802154_llsec_add_devkey), - IEEE802154_OP(IEEE802154_LLSEC_DEL_DEVKEY, ieee802154_llsec_del_devkey), - IEEE802154_DUMP(IEEE802154_LLSEC_LIST_SECLEVEL, NULL, - ieee802154_llsec_dump_seclevels), - IEEE802154_OP(IEEE802154_LLSEC_ADD_SECLEVEL, - ieee802154_llsec_add_seclevel), - IEEE802154_OP(IEEE802154_LLSEC_DEL_SECLEVEL, - ieee802154_llsec_del_seclevel), + IEEE802154_DUMP_PRIV(IEEE802154_LLSEC_LIST_KEY, NULL, + ieee802154_llsec_dump_keys), + IEEE802154_OP_RELAXED(IEEE802154_LLSEC_ADD_KEY, ieee802154_llsec_add_key), + IEEE802154_OP_RELAXED(IEEE802154_LLSEC_DEL_KEY, ieee802154_llsec_del_key), + IEEE802154_DUMP_PRIV(IEEE802154_LLSEC_LIST_DEV, NULL, + ieee802154_llsec_dump_devs), + IEEE802154_OP_RELAXED(IEEE802154_LLSEC_ADD_DEV, ieee802154_llsec_add_dev), + IEEE802154_OP_RELAXED(IEEE802154_LLSEC_DEL_DEV, ieee802154_llsec_del_dev), + IEEE802154_DUMP_PRIV(IEEE802154_LLSEC_LIST_DEVKEY, NULL, + ieee802154_llsec_dump_devkeys), + IEEE802154_OP_RELAXED(IEEE802154_LLSEC_ADD_DEVKEY, ieee802154_llsec_add_devkey), + IEEE802154_OP_RELAXED(IEEE802154_LLSEC_DEL_DEVKEY, ieee802154_llsec_del_devkey), + IEEE802154_DUMP_PRIV(IEEE802154_LLSEC_LIST_SECLEVEL, NULL, + ieee802154_llsec_dump_seclevels), + IEEE802154_OP_RELAXED(IEEE802154_LLSEC_ADD_SECLEVEL, + ieee802154_llsec_add_seclevel), + IEEE802154_OP_RELAXED(IEEE802154_LLSEC_DEL_SECLEVEL, + ieee802154_llsec_del_seclevel), }; static const struct genl_multicast_group ieee802154_mcgrps[] = { diff --git a/net/ieee802154/socket.c b/net/ieee802154/socket.c index 18d267921bb5..dc0de5ae6afb 100644 --- a/net/ieee802154/socket.c +++ b/net/ieee802154/socket.c @@ -313,7 +313,7 @@ out: } static int raw_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { size_t copied = 0; int err = -EOPNOTSUPP; @@ -703,7 +703,7 @@ out: } static int dgram_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { size_t copied = 0; int err = -EOPNOTSUPP; @@ -737,7 +737,7 @@ static int dgram_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, saddr->family = AF_IEEE802154; ieee802154_addr_to_sa(&saddr->addr, &mac_cb(skb)->source); - *addr_len = sizeof(*saddr); + msg->msg_namelen = sizeof(*saddr); } if (ro->want_lqi) { diff --git a/net/ife/ife.c b/net/ife/ife.c index be05b690b9ef..7a75947a31e3 100644 --- a/net/ife/ife.c +++ b/net/ife/ife.c @@ -79,7 +79,7 @@ void *ife_decode(struct sk_buff *skb, u16 *metalen) if (unlikely(ifehdrln < 2)) return NULL; - if (unlikely(!pskb_may_pull(skb, total_pull))) + if (unlikely(!pskb_may_pull(skb, total_pull + ETH_HLEN))) return NULL; ifehdr = (struct ifeheadr *)(skb->data + skb->dev->hard_header_len); diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c index 3109c5ec38f3..4f105a0d0cfc 100644 --- a/net/ipv4/af_inet.c +++ b/net/ipv4/af_inet.c @@ -871,22 +871,17 @@ void inet_splice_eof(struct socket *sock) EXPORT_SYMBOL_GPL(inet_splice_eof); INDIRECT_CALLABLE_DECLARE(int udp_recvmsg(struct sock *, struct msghdr *, - size_t, int, int *)); + size_t, int)); int inet_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, int flags) { struct sock *sk = sock->sk; - int addr_len = 0; - int err; if (likely(!(flags & MSG_ERRQUEUE))) sock_rps_record_flow(sk); - err = INDIRECT_CALL_2(sk->sk_prot->recvmsg, tcp_recvmsg, udp_recvmsg, - sk, msg, size, flags, &addr_len); - if (err >= 0) - msg->msg_namelen = addr_len; - return err; + return INDIRECT_CALL_2(sk->sk_prot->recvmsg, tcp_recvmsg, udp_recvmsg, + sk, msg, size, flags); } EXPORT_SYMBOL(inet_recvmsg); @@ -1571,15 +1566,15 @@ __be32 inet_current_timestamp(void) } EXPORT_SYMBOL(inet_current_timestamp); -int inet_recv_error(struct sock *sk, struct msghdr *msg, int len, int *addr_len) +int inet_recv_error(struct sock *sk, struct msghdr *msg, int len) { unsigned int family = READ_ONCE(sk->sk_family); if (family == AF_INET) - return ip_recv_error(sk, msg, len, addr_len); + return ip_recv_error(sk, msg, len); #if IS_ENABLED(CONFIG_IPV6) if (family == AF_INET6) - return pingv6_ops.ipv6_recv_error(sk, msg, len, addr_len); + return pingv6_ops.ipv6_recv_error(sk, msg, len); #endif return -EINVAL; } diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index 449cb4ba6153..aca035eb5892 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -204,13 +204,18 @@ static void ip_sf_list_clear_all(struct ip_sf_list *psf) static void igmp_stop_timer(struct ip_mc_list *im) { + bool put = false; + spin_lock_bh(&im->lock); if (timer_delete(&im->timer)) - refcount_dec(&im->refcnt); - im->tm_running = 0; - im->reporter = 0; + put = true; + WRITE_ONCE(im->tm_running, 0); + WRITE_ONCE(im->reporter, 0); im->unsolicit_count = 0; spin_unlock_bh(&im->lock); + + if (put) + ip_ma_put(im); } /* It must be called with locked im->lock */ @@ -218,7 +223,7 @@ static void igmp_start_timer(struct ip_mc_list *im, int max_delay) { int tv = get_random_u32_below(max_delay); - im->tm_running = 1; + WRITE_ONCE(im->tm_running, 1); if (refcount_inc_not_zero(&im->refcnt)) { if (mod_timer(&im->timer, jiffies + tv + 2)) ip_ma_put(im); @@ -235,33 +240,42 @@ static void igmp_gq_start_timer(struct in_device *in_dev) return; in_dev->mr_gq_running = 1; - if (!mod_timer(&in_dev->mr_gq_timer, exp)) - in_dev_hold(in_dev); + if (in_dev_hold_safe(in_dev)) { + if (mod_timer(&in_dev->mr_gq_timer, exp)) + in_dev_put(in_dev); + } } static void igmp_ifc_start_timer(struct in_device *in_dev, int delay) { - int tv = get_random_u32_below(delay); + if (in_dev_hold_safe(in_dev)) { + int tv = get_random_u32_below(delay); - if (!mod_timer(&in_dev->mr_ifc_timer, jiffies+tv+2)) - in_dev_hold(in_dev); + if (mod_timer(&in_dev->mr_ifc_timer, jiffies + tv + 2)) + in_dev_put(in_dev); + } } static void igmp_mod_timer(struct ip_mc_list *im, int max_delay) { + bool put = false; + spin_lock_bh(&im->lock); im->unsolicit_count = 0; if (timer_delete(&im->timer)) { if ((long)(im->timer.expires-jiffies) < max_delay) { add_timer(&im->timer); - im->tm_running = 1; + WRITE_ONCE(im->tm_running, 1); spin_unlock_bh(&im->lock); return; } - refcount_dec(&im->refcnt); + put = true; } igmp_start_timer(im, max_delay); spin_unlock_bh(&im->lock); + + if (put) + ip_ma_put(im); } @@ -844,12 +858,12 @@ static void igmp_timer_expire(struct timer_list *t) struct in_device *in_dev = im->interface; spin_lock(&im->lock); - im->tm_running = 0; + WRITE_ONCE(im->tm_running, 0); if (im->unsolicit_count && --im->unsolicit_count) igmp_start_timer(im, unsolicited_report_interval(in_dev)); - im->reporter = 1; + WRITE_ONCE(im->reporter, 1); spin_unlock(&im->lock); if (IGMP_V1_SEEN(in_dev)) @@ -1311,7 +1325,7 @@ static void __igmp_group_dropped(struct ip_mc_list *im, gfp_t gfp) !READ_ONCE(net->ipv4.sysctl_igmp_llm_reports)) return; - reporter = im->reporter; + reporter = READ_ONCE(im->reporter); igmp_stop_timer(im); if (!in_dev->dead) { @@ -1527,7 +1541,7 @@ static void ____ip_mc_inc_group(struct in_device *in_dev, __be32 addr, } if (im) { - im->users++; + WRITE_ONCE(im->users, im->users + 1); ip_mc_add_src(in_dev, &addr, mode, 0, NULL, 0); goto out; } @@ -1536,7 +1550,7 @@ static void ____ip_mc_inc_group(struct in_device *in_dev, __be32 addr, if (!im) goto out; - im->users = 1; + WRITE_ONCE(im->users, 1); im->interface = in_dev; in_dev_hold(in_dev); im->multiaddr = addr; @@ -1770,7 +1784,10 @@ void __ip_mc_dec_group(struct in_device *in_dev, __be32 addr, gfp_t gfp) (i = rtnl_dereference(*ip)) != NULL; ip = &i->next_rcu) { if (i->multiaddr == addr) { - if (--i->users == 0) { + int new_users = i->users - 1; + + WRITE_ONCE(i->users, new_users); + if (new_users == 0) { ip_mc_hash_remove(in_dev, i); *ip = i->next_rcu; in_dev->mc_count--; @@ -2946,6 +2963,7 @@ static int igmp_mc_seq_show(struct seq_file *seq, void *v) struct ip_mc_list *im = v; struct igmp_mc_iter_state *state = igmp_mc_seq_private(seq); char *querier; + int tm_running; long delta; #ifdef CONFIG_IP_MULTICAST @@ -2961,13 +2979,14 @@ static int igmp_mc_seq_show(struct seq_file *seq, void *v) state->dev->ifindex, state->dev->name, state->in_dev->mc_count, querier); } - delta = im->timer.expires - jiffies; + tm_running = READ_ONCE(im->tm_running); + delta = READ_ONCE(im->timer.expires) - jiffies; seq_printf(seq, "\t\t\t\t%08X %5d %d:%08lX\t\t%d\n", - im->multiaddr, im->users, - im->tm_running, - im->tm_running ? jiffies_delta_to_clock_t(delta) : 0, - im->reporter); + im->multiaddr, READ_ONCE(im->users), + tm_running, + tm_running ? jiffies_delta_to_clock_t(delta) : 0, + READ_ONCE(im->reporter)); } return 0; } diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index c777895a720e..1679bfefa560 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -1163,6 +1163,9 @@ static bool reqsk_queue_hash_req(struct request_sock *req, /* The timer needs to be setup after a successful insertion. */ timer_setup(&req->rsk_timer, reqsk_timer_handler, TIMER_PINNED); + + preempt_disable_nested(); + mod_timer(&req->rsk_timer, jiffies + timeout); /* before letting lookups find us, make sure all req fields @@ -1170,6 +1173,9 @@ static bool reqsk_queue_hash_req(struct request_sock *req, */ smp_wmb(); refcount_set(&req->rsk_refcnt, 2 + 1); + + preempt_enable_nested(); + return true; } @@ -1295,6 +1301,7 @@ EXPORT_SYMBOL(inet_csk_destroy_sock); void inet_csk_prepare_for_destroy_sock(struct sock *sk) { /* The below has to be done to allow calling inet_csk_destroy_sock */ + tcp_clear_sock_ops_cb_flags(sk); sock_set_flag(sk, SOCK_DEAD); tcp_orphan_count_inc(); } diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c index 6d9c5c20b1c4..7d4f1face2e7 100644 --- a/net/ipv4/ip_sockglue.c +++ b/net/ipv4/ip_sockglue.c @@ -520,7 +520,7 @@ static bool ipv4_datagram_support_cmsg(const struct sock *sk, /* * Handle MSG_ERRQUEUE */ -int ip_recv_error(struct sock *sk, struct msghdr *msg, int len, int *addr_len) +int ip_recv_error(struct sock *sk, struct msghdr *msg, int len) { struct sock_exterr_skb *serr; struct sk_buff *skb; @@ -557,7 +557,7 @@ int ip_recv_error(struct sock *sk, struct msghdr *msg, int len, int *addr_len) serr->addr_offset); sin->sin_port = serr->port; memset(&sin->sin_zero, 0, sizeof(sin->sin_zero)); - *addr_len = sizeof(*sin); + msg->msg_namelen = sizeof(*sin); } memcpy(&errhdr.ee, &serr->ee, sizeof(struct sock_extended_err)); diff --git a/net/ipv4/ip_vti.c b/net/ipv4/ip_vti.c index 95b6bb78fcd2..3b80929994a0 100644 --- a/net/ipv4/ip_vti.c +++ b/net/ipv4/ip_vti.c @@ -596,6 +596,9 @@ static int vti_changelink(struct net_device *dev, struct nlattr *tb[], struct ip_tunnel_parm_kern p; __u32 fwmark = t->fwmark; + if (!rtnl_dev_link_net_capable(dev, t->net)) + return -EPERM; + vti_netlink_parms(data, &p, &fwmark); return ip_tunnel_changelink(dev, tb, &p, fwmark); } diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c index 3e03af073a1c..94e974c201f0 100644 --- a/net/ipv4/ipip.c +++ b/net/ipv4/ipip.c @@ -469,6 +469,9 @@ static int ipip_changelink(struct net_device *dev, struct nlattr *tb[], bool collect_md; __u32 fwmark = t->fwmark; + if (!rtnl_dev_link_net_capable(dev, t->net)) + return -EPERM; + if (ip_tunnel_netlink_encap_parms(data, &ipencap)) { int err = ip_tunnel_encap_setup(t, &ipencap); diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c index fae4aa4a5f09..c47d56576720 100644 --- a/net/ipv4/netfilter/nf_reject_ipv4.c +++ b/net/ipv4/netfilter/nf_reject_ipv4.c @@ -89,7 +89,7 @@ static bool nf_skb_is_icmp_unreach(const struct sk_buff *skb) if (iph->protocol != IPPROTO_ICMP) return false; - thoff = skb_network_offset(skb) + sizeof(*iph); + thoff = skb_network_offset(skb) + ip_hdrlen(skb); tp = skb_header_pointer(skb, thoff + offsetof(struct icmphdr, type), diff --git a/net/ipv4/ping.c b/net/ipv4/ping.c index 690f486173e0..7f32753a16f4 100644 --- a/net/ipv4/ping.c +++ b/net/ipv4/ping.c @@ -847,8 +847,7 @@ do_confirm: goto out; } -int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags, - int *addr_len) +int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags) { struct inet_sock *isk = inet_sk(sk); int family = sk->sk_family; @@ -863,7 +862,7 @@ int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags, goto out; if (flags & MSG_ERRQUEUE) - return inet_recv_error(sk, msg, len, addr_len); + return inet_recv_error(sk, msg, len); skb = skb_recv_datagram(sk, flags, &err); if (!skb) @@ -891,7 +890,7 @@ int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags, sin->sin_port = 0 /* skb->h.uh->source */; sin->sin_addr.s_addr = ip_hdr(skb)->saddr; memset(sin->sin_zero, 0, sizeof(sin->sin_zero)); - *addr_len = sizeof(*sin); + msg->msg_namelen = sizeof(*sin); } if (inet_cmsg_flags(isk)) @@ -912,7 +911,7 @@ int ping_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags, sin6->sin6_scope_id = ipv6_iface_scope_id(&sin6->sin6_addr, inet6_iif(skb)); - *addr_len = sizeof(*sin6); + msg->msg_namelen = sizeof(*sin6); } if (inet6_sk(sk)->rxopt.all) diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c index f79f4c29a043..26468a2ec63d 100644 --- a/net/ipv4/raw.c +++ b/net/ipv4/raw.c @@ -737,7 +737,7 @@ out: */ static int raw_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { struct inet_sock *inet = inet_sk(sk); size_t copied = 0; @@ -749,7 +749,7 @@ static int raw_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, goto out; if (flags & MSG_ERRQUEUE) { - err = ip_recv_error(sk, msg, len, addr_len); + err = ip_recv_error(sk, msg, len); goto out; } @@ -775,7 +775,7 @@ static int raw_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, sin->sin_addr.s_addr = ip_hdr(skb)->saddr; sin->sin_port = 0; memset(&sin->sin_zero, 0, sizeof(sin->sin_zero)); - *addr_len = sizeof(*sin); + msg->msg_namelen = sizeof(*sin); } if (inet_cmsg_flags(inet)) ip_cmsg_recv(msg, skb); diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 6fc00b38695b..db80b81a74cb 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -2909,14 +2909,13 @@ recv_sndq: goto out; } -int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags, - int *addr_len) +int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags) { int cmsg_flags = 0, ret; struct scm_timestamping_internal tss; if (unlikely(flags & MSG_ERRQUEUE)) - return inet_recv_error(sk, msg, len, addr_len); + return inet_recv_error(sk, msg, len); if (sk_can_busy_loop(sk) && skb_queue_empty_lockless(&sk->sk_receive_queue) && diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c index d3d6a47af527..7a3f0b244c8f 100644 --- a/net/ipv4/tcp_bpf.c +++ b/net/ipv4/tcp_bpf.c @@ -221,8 +221,7 @@ static bool is_next_msg_fin(struct sk_psock *psock) static int tcp_bpf_recvmsg_parser(struct sock *sk, struct msghdr *msg, size_t len, - int flags, - int *addr_len) + int flags) { int peek = flags & MSG_PEEK; struct sk_psock *psock; @@ -232,14 +231,14 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, u32 seq; if (unlikely(flags & MSG_ERRQUEUE)) - return inet_recv_error(sk, msg, len, addr_len); + return inet_recv_error(sk, msg, len); if (!len) return 0; psock = sk_psock_get(sk); if (unlikely(!psock)) - return tcp_recvmsg(sk, msg, len, flags, addr_len); + return tcp_recvmsg(sk, msg, len, flags); lock_sock(sk); tcp = tcp_sk(sk); @@ -352,24 +351,24 @@ static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg) } static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { struct sk_psock *psock; int copied, ret; if (unlikely(flags & MSG_ERRQUEUE)) - return inet_recv_error(sk, msg, len, addr_len); + return inet_recv_error(sk, msg, len); if (!len) return 0; psock = sk_psock_get(sk); if (unlikely(!psock)) - return tcp_recvmsg(sk, msg, len, flags, addr_len); + return tcp_recvmsg(sk, msg, len, flags); if (!skb_queue_empty(&sk->sk_receive_queue) && sk_psock_queue_empty(psock)) { sk_psock_put(sk, psock); - return tcp_recvmsg(sk, msg, len, flags, addr_len); + return tcp_recvmsg(sk, msg, len, flags); } lock_sock(sk); msg_bytes_ready: @@ -389,7 +388,7 @@ msg_bytes_ready: goto msg_bytes_ready; release_sock(sk); sk_psock_put(sk, psock); - return tcp_recvmsg(sk, msg, len, flags, addr_len); + return tcp_recvmsg(sk, msg, len, flags); } copied = -EAGAIN; } diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 36206fc6aed2..f0106c2be1cf 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -1505,9 +1505,9 @@ void tcp_clear_md5_list(struct sock *sk) md5sig = rcu_dereference_protected(tp->md5sig_info, 1); hlist_for_each_entry_safe(key, n, &md5sig->head, node) { - hlist_del(&key->node); + hlist_del_rcu(&key->node); atomic_sub(sizeof(*key), &sk->sk_omem_alloc); - kfree(key); + kfree_rcu(key, rcu); } } diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index 318d548b606e..479be2f24262 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -4263,9 +4263,13 @@ int tcp_connect(struct sock *sk) if (needs_md5) { tcp_ao_destroy_sock(sk, false); } else if (needs_ao) { + struct tcp_md5sig_info *md5sig; + tcp_clear_md5_list(sk); - kfree(rcu_replace_pointer(tp->md5sig_info, NULL, - lockdep_sock_is_held(sk))); + md5sig = rcu_replace_pointer(tp->md5sig_info, NULL, + lockdep_sock_is_held(sk)); + kfree_rcu(md5sig, rcu); + static_branch_slow_dec_deferred(&tcp_md5_needed); } } #endif diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 4ac4a120fca8..2f5c91ba0ce9 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -2059,8 +2059,7 @@ EXPORT_IPV6_MOD(udp_read_skb); * return it, otherwise we block. */ -int udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags, - int *addr_len) +int udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags) { struct inet_sock *inet = inet_sk(sk); DECLARE_SOCKADDR(struct sockaddr_in *, sin, msg->msg_name); @@ -2071,7 +2070,7 @@ int udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags, bool checksum_valid = false; if (flags & MSG_ERRQUEUE) - return ip_recv_error(sk, msg, len, addr_len); + return ip_recv_error(sk, msg, len); try_again: off = sk_peek_offset(sk, flags); @@ -2134,11 +2133,11 @@ try_again: sin->sin_port = udp_hdr(skb)->source; sin->sin_addr.s_addr = ip_hdr(skb)->saddr; memset(sin->sin_zero, 0, sizeof(sin->sin_zero)); - *addr_len = sizeof(*sin); + msg->msg_namelen = sizeof(*sin); BPF_CGROUP_RUN_PROG_UDP4_RECVMSG_LOCK(sk, (struct sockaddr *)sin, - addr_len); + &msg->msg_namelen); } if (udp_test_bit(GRO_ENABLED, sk)) diff --git a/net/ipv4/udp_bpf.c b/net/ipv4/udp_bpf.c index 779a3a03762f..6987b8cf59eb 100644 --- a/net/ipv4/udp_bpf.c +++ b/net/ipv4/udp_bpf.c @@ -12,13 +12,13 @@ static struct proto *udpv6_prot_saved __read_mostly; static int sk_udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { #if IS_ENABLED(CONFIG_IPV6) if (sk->sk_family == AF_INET6) - return udpv6_prot_saved->recvmsg(sk, msg, len, flags, addr_len); + return udpv6_prot_saved->recvmsg(sk, msg, len, flags); #endif - return udp_prot.recvmsg(sk, msg, len, flags, addr_len); + return udp_prot.recvmsg(sk, msg, len, flags); } static bool udp_sk_has_data(struct sock *sk) @@ -52,7 +52,9 @@ static int udp_msg_wait_data(struct sock *sk, struct sk_psock *psock, sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk); ret = udp_msg_has_data(sk, psock); if (!ret) { + release_sock(sk); wait_woken(&wait, TASK_INTERRUPTIBLE, timeo); + lock_sock(sk); ret = udp_msg_has_data(sk, psock); } sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk); @@ -61,26 +63,27 @@ static int udp_msg_wait_data(struct sock *sk, struct sk_psock *psock, } static int udp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { struct sk_psock *psock; int copied, ret; if (unlikely(flags & MSG_ERRQUEUE)) - return inet_recv_error(sk, msg, len, addr_len); + return inet_recv_error(sk, msg, len); if (!len) return 0; psock = sk_psock_get(sk); if (unlikely(!psock)) - return sk_udp_recvmsg(sk, msg, len, flags, addr_len); + return sk_udp_recvmsg(sk, msg, len, flags); if (!psock_has_data(psock)) { - ret = sk_udp_recvmsg(sk, msg, len, flags, addr_len); + ret = sk_udp_recvmsg(sk, msg, len, flags); goto out; } + lock_sock(sk); msg_bytes_ready: copied = sk_msg_recvmsg(sk, psock, msg, len, flags); if (!copied) { @@ -92,11 +95,17 @@ msg_bytes_ready: if (data) { if (psock_has_data(psock)) goto msg_bytes_ready; - ret = sk_udp_recvmsg(sk, msg, len, flags, addr_len); + + release_sock(sk); + + ret = sk_udp_recvmsg(sk, msg, len, flags); goto out; } copied = -EAGAIN; } + + release_sock(sk); + ret = copied; out: sk_psock_put(sk, psock); diff --git a/net/ipv4/udp_impl.h b/net/ipv4/udp_impl.h index c7142213fc21..17a6fa8b1409 100644 --- a/net/ipv4/udp_impl.h +++ b/net/ipv4/udp_impl.h @@ -18,8 +18,7 @@ int udp_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval, int udp_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen); -int udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags, - int *addr_len); +int udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags); void udp_destroy_sock(struct sock *sk); #ifdef CONFIG_PROC_FS diff --git a/net/ipv4/udp_tunnel_nic.c b/net/ipv4/udp_tunnel_nic.c index 944b3cf25468..b13e8f7092f4 100644 --- a/net/ipv4/udp_tunnel_nic.c +++ b/net/ipv4/udp_tunnel_nic.c @@ -301,7 +301,7 @@ __udp_tunnel_nic_device_sync(struct net_device *dev, struct udp_tunnel_nic *utn) static void udp_tunnel_nic_device_sync(struct net_device *dev, struct udp_tunnel_nic *utn) { - if (!utn->need_sync) + if (!utn->need_sync || utn->work_pending) return; queue_work(udp_tunnel_nic_workqueue, &utn->work); diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index b2e1328371d3..c98b1b919f18 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -913,7 +913,7 @@ static int addrconf_fixup_forwarding(const struct ctl_table *table, int *p, int if (newf) rt6_purge_dflt_routers(net); - return 1; + return 0; } static void addrconf_linkdown_change(struct net *net, __s32 newf) @@ -955,11 +955,7 @@ static int addrconf_fixup_linkdown(const struct ctl_table *table, int *p, int ne NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN, NETCONFA_IFINDEX_DEFAULT, net->ipv6.devconf_dflt); - rtnl_net_unlock(net); - return 0; - } - - if (p == &net->ipv6.devconf_all->ignore_routes_with_linkdown) { + } else if (p == &net->ipv6.devconf_all->ignore_routes_with_linkdown) { WRITE_ONCE(net->ipv6.devconf_dflt->ignore_routes_with_linkdown, newf); addrconf_linkdown_change(net, newf); if ((!newf) ^ (!old)) @@ -968,11 +964,21 @@ static int addrconf_fixup_linkdown(const struct ctl_table *table, int *p, int ne NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN, NETCONFA_IFINDEX_ALL, net->ipv6.devconf_all); + } else { + if (!newf ^ !old) { + struct inet6_dev *idev = table->extra1; + + inet6_netconf_notify_devconf(net, + RTM_NEWNETCONF, + NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN, + idev->dev->ifindex, + &idev->cnf); + } } rtnl_net_unlock(net); - return 1; + return 0; } #endif @@ -2168,16 +2174,18 @@ void addrconf_dad_failure(struct sk_buff *skb, struct inet6_ifaddr *ifp) struct net *net = dev_net(idev->dev); int max_addresses; - if (addrconf_dad_end(ifp)) { + spin_lock_bh(&ifp->lock); + + if (ifp->state != INET6_IFADDR_STATE_DAD) { + spin_unlock_bh(&ifp->lock); in6_ifa_put(ifp); return; } + ifp->state = INET6_IFADDR_STATE_POSTDAD; net_info_ratelimited("%s: IPv6 duplicate address %pI6c used by %pM detected!\n", ifp->idev->dev->name, &ifp->addr, eth_hdr(skb)->h_source); - spin_lock_bh(&ifp->lock); - if (ifp->flags & IFA_F_STABLE_PRIVACY) { struct in6_addr new_addr; struct inet6_ifaddr *ifp2; @@ -2225,6 +2233,11 @@ void addrconf_dad_failure(struct sk_buff *skb, struct inet6_ifaddr *ifp) in6_ifa_put(ifp2); lock_errdad: spin_lock_bh(&ifp->lock); + if (ifp->state != INET6_IFADDR_STATE_POSTDAD) { + spin_unlock_bh(&ifp->lock); + in6_ifa_put(ifp); + return; + } } errdad: @@ -6343,6 +6356,8 @@ static int addrconf_sysctl_forward(const struct ctl_table *ctl, int write, lctl.data = &val; ret = proc_dointvec(&lctl, write, buffer, lenp, ppos); + if (ret) + return ret; if (write) ret = addrconf_fixup_forwarding(ctl, valp, val); @@ -6440,6 +6455,8 @@ static int addrconf_sysctl_disable(const struct ctl_table *ctl, int write, lctl.data = &val; ret = proc_dointvec(&lctl, write, buffer, lenp, ppos); + if (ret) + return ret; if (write) ret = addrconf_disable_ipv6(ctl, valp, val); @@ -6451,20 +6468,19 @@ static int addrconf_sysctl_disable(const struct ctl_table *ctl, int write, static int addrconf_sysctl_proxy_ndp(const struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { + struct net *net = ctl->extra2; int *valp = ctl->data; - int ret; int old, new; + int ret; + + if (write && !rtnl_net_trylock(net)) + return restart_syscall(); old = *valp; ret = proc_dointvec(ctl, write, buffer, lenp, ppos); new = *valp; if (write && old != new) { - struct net *net = ctl->extra2; - - if (!rtnl_net_trylock(net)) - return restart_syscall(); - if (valp == &net->ipv6.devconf_dflt->proxy_ndp) { inet6_netconf_notify_devconf(net, RTM_NEWNETCONF, NETCONFA_PROXY_NEIGH, @@ -6483,8 +6499,9 @@ static int addrconf_sysctl_proxy_ndp(const struct ctl_table *ctl, int write, idev->dev->ifindex, &idev->cnf); } - rtnl_net_unlock(net); } + if (write) + rtnl_net_unlock(net); return ret; } @@ -6642,6 +6659,8 @@ int addrconf_sysctl_ignore_routes_with_linkdown(const struct ctl_table *ctl, lctl.data = &val; ret = proc_dointvec(&lctl, write, buffer, lenp, ppos); + if (ret) + return ret; if (write) ret = addrconf_fixup_linkdown(ctl, valp, val); @@ -6736,6 +6755,8 @@ static int addrconf_sysctl_disable_policy(const struct ctl_table *ctl, int write lctl = *ctl; lctl.data = &val; ret = proc_dointvec(&lctl, write, buffer, lenp, ppos); + if (ret) + return ret; if (write && (*valp != val)) ret = addrconf_disable_policy(ctl, valp, val); diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c index 3709f213d33d..f8e1dc4f3227 100644 --- a/net/ipv6/af_inet6.c +++ b/net/ipv6/af_inet6.c @@ -661,25 +661,20 @@ int inet6_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) } INDIRECT_CALLABLE_DECLARE(int udpv6_recvmsg(struct sock *, struct msghdr *, - size_t, int, int *)); + size_t, int)); int inet6_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, int flags) { struct sock *sk = sock->sk; const struct proto *prot; - int addr_len = 0; - int err; if (likely(!(flags & MSG_ERRQUEUE))) sock_rps_record_flow(sk); /* IPV6_ADDRFORM can change sk->sk_prot under us. */ prot = READ_ONCE(sk->sk_prot); - err = INDIRECT_CALL_2(prot->recvmsg, tcp_recvmsg, udpv6_recvmsg, - sk, msg, size, flags, &addr_len); - if (err >= 0) - msg->msg_namelen = addr_len; - return err; + return INDIRECT_CALL_2(prot->recvmsg, tcp_recvmsg, udpv6_recvmsg, + sk, msg, size, flags); } const struct proto_ops inet6_stream_ops = { diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c index a62c20e8329f..97c655c26056 100644 --- a/net/ipv6/datagram.c +++ b/net/ipv6/datagram.c @@ -451,7 +451,7 @@ static bool ip6_datagram_support_cmsg(struct sk_buff *skb, /* * Handle MSG_ERRQUEUE */ -int ipv6_recv_error(struct sock *sk, struct msghdr *msg, int len, int *addr_len) +int ipv6_recv_error(struct sock *sk, struct msghdr *msg, int len) { struct ipv6_pinfo *np = inet6_sk(sk); struct sock_exterr_skb *serr; @@ -502,7 +502,7 @@ int ipv6_recv_error(struct sock *sk, struct msghdr *msg, int len, int *addr_len) &sin->sin6_addr); sin->sin6_scope_id = 0; } - *addr_len = sizeof(*sin); + msg->msg_namelen = sizeof(*sin); } memcpy(&errhdr.ee, &serr->ee, sizeof(struct sock_extended_err)); @@ -544,8 +544,7 @@ EXPORT_SYMBOL_GPL(ipv6_recv_error); /* * Handle IPV6_RECVPATHMTU */ -int ipv6_recv_rxpmtu(struct sock *sk, struct msghdr *msg, int len, - int *addr_len) +int ipv6_recv_rxpmtu(struct sock *sk, struct msghdr *msg, int len) { struct ipv6_pinfo *np = inet6_sk(sk); struct sk_buff *skb; @@ -578,7 +577,7 @@ int ipv6_recv_rxpmtu(struct sock *sk, struct msghdr *msg, int len, sin->sin6_port = 0; sin->sin6_scope_id = mtu_info.ip6m_addr.sin6_scope_id; sin->sin6_addr = mtu_info.ip6m_addr.sin6_addr; - *addr_len = sizeof(*sin); + msg->msg_namelen = sizeof(*sin); } put_cmsg(msg, SOL_IPV6, IPV6_PATHMTU, sizeof(mtu_info), &mtu_info); diff --git a/net/ipv6/ioam6_iptunnel.c b/net/ipv6/ioam6_iptunnel.c index b9f6d892a566..cfb2c41634a0 100644 --- a/net/ipv6/ioam6_iptunnel.c +++ b/net/ipv6/ioam6_iptunnel.c @@ -35,7 +35,7 @@ struct ioam6_lwt_freq { }; struct ioam6_lwt { - struct dst_entry null_dst; + struct rt6_info null_rt; struct dst_cache cache; struct ioam6_lwt_freq freq; atomic_t pkt_cnt; @@ -176,7 +176,7 @@ static int ioam6_build_state(struct net *net, struct nlattr *nla, * it is stored in the cache. Then, +1/-1 each time we read the cache * and release it. Long story short, we're fine. */ - dst_init(&ilwt->null_dst, NULL, NULL, DST_OBSOLETE_NONE, DST_NOCOUNT); + dst_init(&ilwt->null_rt.dst, NULL, NULL, DST_OBSOLETE_NONE, DST_NOCOUNT); atomic_set(&ilwt->pkt_cnt, 0); ilwt->freq.k = freq_k; @@ -360,7 +360,7 @@ static int ioam6_output(struct net *net, struct sock *sk, struct sk_buff *skb) /* This is how we notify that the destination does not change after * transformation and that we need to use orig_dst instead of the cache */ - if (dst == &ilwt->null_dst) { + if (dst == &ilwt->null_rt.dst) { dst_release(dst); dst = orig_dst; @@ -429,7 +429,7 @@ do_encap: local_bh_disable(); if (orig_dst->lwtstate == dst->lwtstate) dst_cache_set_ip6(&ilwt->cache, - &ilwt->null_dst, &fl6.saddr); + &ilwt->null_rt.dst, &fl6.saddr); else dst_cache_set_ip6(&ilwt->cache, dst, &fl6.saddr); local_bh_enable(); diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index ffa773359833..940aae3d10be 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -634,12 +634,12 @@ static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb) }; const struct nlmsghdr *nlh = cb->nlh; struct net *net = sock_net(skb->sk); - unsigned int e = 0, s_e; struct hlist_head *head; struct fib6_walker *w; struct fib6_table *tb; unsigned int h, s_h; int err = 0; + u32 s_id; rcu_read_lock(); if (cb->strict_check) { @@ -699,23 +699,22 @@ static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb) } s_h = cb->args[0]; - s_e = cb->args[1]; + s_id = cb->args[1]; - for (h = s_h; h < FIB6_TABLE_HASHSZ; h++, s_e = 0) { - e = 0; + for (h = s_h; h < FIB6_TABLE_HASHSZ; h++, s_id = 0) { head = &net->ipv6.fib_table_hash[h]; hlist_for_each_entry_rcu(tb, head, tb6_hlist) { - if (e < s_e) - goto next; + if (s_id && tb->tb6_id != s_id) + continue; + + s_id = 0; + cb->args[1] = tb->tb6_id; err = fib6_dump_table(tb, skb, cb); if (err != 0) goto out; -next: - e++; } } out: - cb->args[1] = e; cb->args[0] = h; unlock: diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index 6dea7caf5a77..75961c4ebcdd 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -2046,6 +2046,9 @@ static int ip6gre_changelink(struct net_device *dev, struct nlattr *tb[], struct ip6gre_net *ign = net_generic(t->net, ip6gre_net_id); struct __ip6_tnl_parm p; + if (!rtnl_dev_link_net_capable(dev, t->net)) + return -EPERM; + t = ip6gre_changelink_common(dev, tb, data, &p, extack); if (IS_ERR(t)) return PTR_ERR(t); @@ -2265,6 +2268,9 @@ static int ip6erspan_changelink(struct net_device *dev, struct nlattr *tb[], struct __ip6_tnl_parm p; struct ip6gre_net *ign; + if (!rtnl_dev_link_net_capable(dev, t->net)) + return -EPERM; + ign = net_generic(t->net, ip6gre_net_id); t = ip6gre_changelink_common(dev, tb, data, &p, extack); if (IS_ERR(t)) diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index 9e2449db0bdf..cbc984a32a29 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -2068,6 +2068,9 @@ static int ip6_tnl_changelink(struct net_device *dev, struct nlattr *tb[], struct ip6_tnl_net *ip6n = net_generic(net, ip6_tnl_net_id); struct ip_tunnel_encap ipencap; + if (!rtnl_dev_link_net_capable(dev, net)) + return -EPERM; + if (dev == ip6n->fb_tnl_dev) { if (ip_tunnel_netlink_encap_parms(data, &ipencap)) { /* iproute2 always sets TUNNEL_ENCAP_FLAG_CSUM6, so diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c index d871cab6938d..ab94b3a4ba9c 100644 --- a/net/ipv6/ip6_vti.c +++ b/net/ipv6/ip6_vti.c @@ -1046,6 +1046,9 @@ static int vti6_changelink(struct net_device *dev, struct nlattr *tb[], struct __ip6_tnl_parm p; struct vti6_net *ip6n; + if (!rtnl_dev_link_net_capable(dev, net)) + return -EPERM; + ip6n = net_generic(net, vti6_net_id); if (dev == ip6n->fb_tnl_dev) return -EINVAL; diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index f4b3cb483870..c63f97c79c49 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -1080,8 +1080,10 @@ static void mld_gq_start_work(struct inet6_dev *idev) mc_assert_locked(idev); idev->mc_gq_running = 1; - if (!mod_delayed_work(mld_wq, &idev->mc_gq_work, tv + 2)) - in6_dev_hold(idev); + if (in6_dev_hold_safe(idev)) { + if (mod_delayed_work(mld_wq, &idev->mc_gq_work, tv + 2)) + in6_dev_put(idev); + } } static void mld_gq_stop_work(struct inet6_dev *idev) @@ -1099,8 +1101,10 @@ static void mld_ifc_start_work(struct inet6_dev *idev, unsigned long delay) mc_assert_locked(idev); - if (!mod_delayed_work(mld_wq, &idev->mc_ifc_work, tv + 2)) - in6_dev_hold(idev); + if (in6_dev_hold_safe(idev)) { + if (mod_delayed_work(mld_wq, &idev->mc_ifc_work, tv + 2)) + in6_dev_put(idev); + } } static void mld_ifc_stop_work(struct inet6_dev *idev) @@ -1118,8 +1122,10 @@ static void mld_dad_start_work(struct inet6_dev *idev, unsigned long delay) mc_assert_locked(idev); - if (!mod_delayed_work(mld_wq, &idev->mc_dad_work, tv + 2)) - in6_dev_hold(idev); + if (in6_dev_hold_safe(idev)) { + if (mod_delayed_work(mld_wq, &idev->mc_dad_work, tv + 2)) + in6_dev_put(idev); + } } static void mld_dad_stop_work(struct inet6_dev *idev) @@ -1405,18 +1411,23 @@ static void mld_process_v2(struct inet6_dev *idev, struct mld2_query *mld, void igmp6_event_query(struct sk_buff *skb) { struct inet6_dev *idev = __in6_dev_get(skb->dev); + bool put = false; if (!idev || idev->dead) goto out; spin_lock_bh(&idev->mc_query_lock); - if (skb_queue_len(&idev->mc_query_queue) < MLD_MAX_SKBS) { + if (skb_queue_len(&idev->mc_query_queue) < MLD_MAX_SKBS && + in6_dev_hold_safe(idev)) { __skb_queue_tail(&idev->mc_query_queue, skb); - if (!mod_delayed_work(mld_wq, &idev->mc_query_work, 0)) - in6_dev_hold(idev); + if (mod_delayed_work(mld_wq, &idev->mc_query_work, 0)) + put = true; skb = NULL; } spin_unlock_bh(&idev->mc_query_lock); + + if (put) + in6_dev_put(idev); out: kfree_skb(skb); } @@ -1573,18 +1584,23 @@ static void mld_query_work(struct work_struct *work) void igmp6_event_report(struct sk_buff *skb) { struct inet6_dev *idev = __in6_dev_get(skb->dev); + bool put = false; if (!idev || idev->dead) goto out; spin_lock_bh(&idev->mc_report_lock); - if (skb_queue_len(&idev->mc_report_queue) < MLD_MAX_SKBS) { + if (skb_queue_len(&idev->mc_report_queue) < MLD_MAX_SKBS && + in6_dev_hold_safe(idev)) { __skb_queue_tail(&idev->mc_report_queue, skb); - if (!mod_delayed_work(mld_wq, &idev->mc_report_work, 0)) - in6_dev_hold(idev); + if (mod_delayed_work(mld_wq, &idev->mc_report_work, 0)) + put = true; skb = NULL; } spin_unlock_bh(&idev->mc_report_lock); + + if (put) + in6_dev_put(idev); out: kfree_skb(skb); } diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c index ded2d3a0660c..9df1fba967c6 100644 --- a/net/ipv6/ndisc.c +++ b/net/ipv6/ndisc.c @@ -966,10 +966,8 @@ out: return reason; } -static int accept_untracked_na(struct net_device *dev, struct in6_addr *saddr) +static int accept_untracked_na(struct inet6_dev *idev, struct in6_addr *saddr) { - struct inet6_dev *idev = __in6_dev_get(dev); - switch (READ_ONCE(idev->cnf.accept_untracked_na)) { case 0: /* Don't accept untracked na (absent in neighbor cache) */ return 0; @@ -979,7 +977,7 @@ static int accept_untracked_na(struct net_device *dev, struct in6_addr *saddr) * same subnet as an address configured on the interface that * received the na */ - return !!ipv6_chk_prefix(saddr, dev); + return !!ipv6_chk_prefix(saddr, idev->dev); default: return 0; } @@ -1077,7 +1075,7 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb) */ new_state = msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE; if (!neigh && lladdr && idev && READ_ONCE(idev->cnf.forwarding)) { - if (accept_untracked_na(dev, saddr)) { + if (accept_untracked_na(idev, saddr)) { neigh = neigh_create(&nd_tbl, &msg->target, dev); new_state = NUD_STALE; } diff --git a/net/ipv6/netfilter.c b/net/ipv6/netfilter.c index 46540a5a4331..c3065439f624 100644 --- a/net/ipv6/netfilter.c +++ b/net/ipv6/netfilter.c @@ -134,7 +134,7 @@ int br_ip6_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, ktime_t tstamp = skb->tstamp; struct ip6_frag_state state; u8 *prevhdr, nexthdr = 0; - unsigned int mtu, hlen; + unsigned int mtu, hlen, nexthdr_offset; int hroom, err = 0; __be32 frag_id; @@ -143,6 +143,7 @@ int br_ip6_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, goto blackhole; hlen = err; nexthdr = *prevhdr; + nexthdr_offset = prevhdr - skb_network_header(skb); mtu = skb->dev->mtu; if (frag_max_size > mtu || @@ -161,6 +162,7 @@ int br_ip6_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, (err = skb_checksum_help(skb))) goto blackhole; + prevhdr = skb_network_header(skb) + nexthdr_offset; hroom = LL_RESERVED_SPACE(skb->dev); if (skb_has_frag_list(skb)) { unsigned int first_len = skb_pagelen(skb); diff --git a/net/ipv6/netfilter/ip6t_ah.c b/net/ipv6/netfilter/ip6t_ah.c index 70da2f2ce064..1258783ed876 100644 --- a/net/ipv6/netfilter/ip6t_ah.c +++ b/net/ipv6/netfilter/ip6t_ah.c @@ -56,6 +56,11 @@ static bool ah_mt6(const struct sk_buff *skb, struct xt_action_param *par) } hdrlen = ipv6_authlen(ah); + if (skb->len - ptr < hdrlen) { + /* Packet smaller than its length field */ + par->hotdrop = true; + return false; + } pr_debug("IPv6 AH LEN %u %u ", hdrlen, ah->hdrlen); pr_debug("RES %04X ", ah->reserved); diff --git a/net/ipv6/netfilter/ip6t_hbh.c b/net/ipv6/netfilter/ip6t_hbh.c index 450dd53846a2..6d1a5d2026a6 100644 --- a/net/ipv6/netfilter/ip6t_hbh.c +++ b/net/ipv6/netfilter/ip6t_hbh.c @@ -75,6 +75,7 @@ hbh_mt6(const struct sk_buff *skb, struct xt_action_param *par) hdrlen = ipv6_optlen(oh); if (skb->len - ptr < hdrlen) { /* Packet smaller than it's length field */ + par->hotdrop = true; return false; } diff --git a/net/ipv6/netfilter/ip6t_rt.c b/net/ipv6/netfilter/ip6t_rt.c index 5561bd9cea81..278b52752f36 100644 --- a/net/ipv6/netfilter/ip6t_rt.c +++ b/net/ipv6/netfilter/ip6t_rt.c @@ -56,7 +56,8 @@ static bool rt_mt6(const struct sk_buff *skb, struct xt_action_param *par) hdrlen = ipv6_optlen(rh); if (skb->len - ptr < hdrlen) { - /* Pcket smaller than its length field */ + /* Packet smaller than its length field */ + par->hotdrop = true; return false; } diff --git a/net/ipv6/netfilter/nf_conntrack_reasm.c b/net/ipv6/netfilter/nf_conntrack_reasm.c index 7edca05dbfc9..599c49bf0a0a 100644 --- a/net/ipv6/netfilter/nf_conntrack_reasm.c +++ b/net/ipv6/netfilter/nf_conntrack_reasm.c @@ -348,7 +348,8 @@ static int nf_ct_frag6_reasm(struct frag_queue *fq, struct sk_buff *skb, skb_network_header(skb)[fq->nhoffset] = skb_transport_header(skb)[0]; memmove(skb->head + sizeof(struct frag_hdr), skb->head, (skb->data - skb->head) - sizeof(struct frag_hdr)); - skb->mac_header += sizeof(struct frag_hdr); + if (skb_mac_header_was_set(skb)) + skb->mac_header += sizeof(struct frag_hdr); skb->network_header += sizeof(struct frag_hdr); skb_reset_transport_header(skb); diff --git a/net/ipv6/ping.c b/net/ipv6/ping.c index d7a2cdaa2631..b74606334c34 100644 --- a/net/ipv6/ping.c +++ b/net/ipv6/ping.c @@ -24,8 +24,7 @@ #include <net/ping.h> /* Compatibility glue so we can support IPv6 when it's compiled as a module */ -static int dummy_ipv6_recv_error(struct sock *sk, struct msghdr *msg, int len, - int *addr_len) +static int dummy_ipv6_recv_error(struct sock *sk, struct msghdr *msg, int len) { return -EAFNOSUPPORT; } diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c index e369f54844dd..c534848933ee 100644 --- a/net/ipv6/raw.c +++ b/net/ipv6/raw.c @@ -431,7 +431,7 @@ int rawv6_rcv(struct sock *sk, struct sk_buff *skb) */ static int rawv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { struct ipv6_pinfo *np = inet6_sk(sk); DECLARE_SOCKADDR(struct sockaddr_in6 *, sin6, msg->msg_name); @@ -443,10 +443,10 @@ static int rawv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, return -EOPNOTSUPP; if (flags & MSG_ERRQUEUE) - return ipv6_recv_error(sk, msg, len, addr_len); + return ipv6_recv_error(sk, msg, len); if (np->rxopt.bits.rxpmtu && READ_ONCE(np->rxpmtu)) - return ipv6_recv_rxpmtu(sk, msg, len, addr_len); + return ipv6_recv_rxpmtu(sk, msg, len); skb = skb_recv_datagram(sk, flags, &err); if (!skb) @@ -480,7 +480,7 @@ static int rawv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, sin6->sin6_flowinfo = 0; sin6->sin6_scope_id = ipv6_iface_scope_id(&sin6->sin6_addr, inet6_iif(skb)); - *addr_len = sizeof(*sin6); + msg->msg_namelen = sizeof(*sin6); } sock_recv_cmsgs(msg, sk, skb); diff --git a/net/ipv6/route.c b/net/ipv6/route.c index f89220929c4e..ef66a3c86feb 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -3268,11 +3268,11 @@ static unsigned int ip6_default_advmss(const struct dst_entry *dst) /* * Maximal non-jumbo IPv6 payload is IPV6_MAXPLEN and * corresponding MSS is IPV6_MAXPLEN - tcp_header_size. - * IPV6_MAXPLEN is also valid and means: "any MSS, - * rely only on pmtu discovery" + * Limit the default MSS to GSO_BY_FRAGS - 1 to avoid + * collision with the GSO_BY_FRAGS magic value (0xFFFF). */ if (mtu > IPV6_MAXPLEN - sizeof(struct tcphdr)) - mtu = IPV6_MAXPLEN; + mtu = min_t(unsigned int, IPV6_MAXPLEN, GSO_BY_FRAGS - 1); return mtu; } @@ -5043,6 +5043,9 @@ static int fib6_nh_mtu_change(struct fib6_nh *nh, void *_arg) struct inet6_dev *idev = __in6_dev_get(arg->dev); u32 mtu = f6i->fib6_pmtu; + if (!idev) + return 0; + if (mtu >= arg->mtu || (mtu < arg->mtu && mtu == idev->cnf.mtu6)) fib6_metric_set(f6i, RTAX_MTU, arg->mtu); diff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c index a5c4c629b788..0246ab6b2c95 100644 --- a/net/ipv6/seg6.c +++ b/net/ipv6/seg6.c @@ -29,6 +29,9 @@ bool seg6_validate_srh(struct ipv6_sr_hdr *srh, int len, bool reduced) int max_last_entry; int trailing; + if (len < sizeof(*srh)) + return false; + if (srh->type != IPV6_SRCRT_TYPE_4) return false; diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c index 6a833ee665e9..c03df1682a63 100644 --- a/net/ipv6/sit.c +++ b/net/ipv6/sit.c @@ -1610,6 +1610,9 @@ static int ipip6_changelink(struct net_device *dev, struct nlattr *tb[], __u32 fwmark = t->fwmark; int err; + if (!rtnl_dev_link_net_capable(dev, net)) + return -EPERM; + if (dev == sitn->fb_tunnel_dev) return -EINVAL; diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index d47b4f5ac870..7ae825cb5413 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c @@ -466,7 +466,7 @@ static int udp6_skb_len(struct sk_buff *skb) */ int udpv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { struct ipv6_pinfo *np = inet6_sk(sk); struct inet_sock *inet = inet_sk(sk); @@ -479,10 +479,10 @@ int udpv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int is_udp4; if (flags & MSG_ERRQUEUE) - return ipv6_recv_error(sk, msg, len, addr_len); + return ipv6_recv_error(sk, msg, len); if (np->rxopt.bits.rxpmtu && READ_ONCE(np->rxpmtu)) - return ipv6_recv_rxpmtu(sk, msg, len, addr_len); + return ipv6_recv_rxpmtu(sk, msg, len); try_again: off = sk_peek_offset(sk, flags); @@ -554,11 +554,11 @@ try_again: ipv6_iface_scope_id(&sin6->sin6_addr, inet6_iif(skb)); } - *addr_len = sizeof(*sin6); + msg->msg_namelen = sizeof(*sin6); BPF_CGROUP_RUN_PROG_UDP6_RECVMSG_LOCK(sk, (struct sockaddr *)sin6, - addr_len); + &msg->msg_namelen); } if (udp_test_bit(GRO_ENABLED, sk)) diff --git a/net/ipv6/udp_impl.h b/net/ipv6/udp_impl.h index 8a406be25a3a..1bd4a573e1bb 100644 --- a/net/ipv6/udp_impl.h +++ b/net/ipv6/udp_impl.h @@ -22,8 +22,7 @@ int udpv6_getsockopt(struct sock *sk, int level, int optname, int udpv6_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval, unsigned int optlen); int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len); -int udpv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags, - int *addr_len); +int udpv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int flags); void udpv6_destroy_sock(struct sock *sk); #ifdef CONFIG_PROC_FS diff --git a/net/kcm/kcmsock.c b/net/kcm/kcmsock.c index f6d44481954c..dc126f7f030c 100644 --- a/net/kcm/kcmsock.c +++ b/net/kcm/kcmsock.c @@ -1304,8 +1304,8 @@ static int kcm_attach(struct socket *sock, struct socket *csock, psock->save_write_space = csk->sk_write_space; psock->save_state_change = csk->sk_state_change; csk->sk_user_data = psock; - csk->sk_data_ready = psock_data_ready; - csk->sk_write_space = psock_write_space; + WRITE_ONCE(csk->sk_data_ready, psock_data_ready); + WRITE_ONCE(csk->sk_write_space, psock_write_space); csk->sk_state_change = psock_state_change; write_unlock_bh(&csk->sk_callback_lock); @@ -1381,8 +1381,8 @@ static void kcm_unattach(struct kcm_psock *psock) */ write_lock_bh(&csk->sk_callback_lock); csk->sk_user_data = NULL; - csk->sk_data_ready = psock->save_data_ready; - csk->sk_write_space = psock->save_write_space; + WRITE_ONCE(csk->sk_data_ready, psock->save_data_ready); + WRITE_ONCE(csk->sk_write_space, psock->save_write_space); csk->sk_state_change = psock->save_state_change; strp_stop(&psock->strp); diff --git a/net/l2tp/l2tp_ip.c b/net/l2tp/l2tp_ip.c index 29795d2839e8..e9565bbdc4f6 100644 --- a/net/l2tp/l2tp_ip.c +++ b/net/l2tp/l2tp_ip.c @@ -535,7 +535,7 @@ no_route: } static int l2tp_ip_recvmsg(struct sock *sk, struct msghdr *msg, - size_t len, int flags, int *addr_len) + size_t len, int flags) { struct inet_sock *inet = inet_sk(sk); size_t copied = 0; @@ -568,7 +568,7 @@ static int l2tp_ip_recvmsg(struct sock *sk, struct msghdr *msg, sin->sin_addr.s_addr = ip_hdr(skb)->saddr; sin->sin_port = 0; memset(&sin->sin_zero, 0, sizeof(sin->sin_zero)); - *addr_len = sizeof(*sin); + msg->msg_namelen = sizeof(*sin); } if (inet_cmsg_flags(inet)) ip_cmsg_recv(msg, skb); diff --git a/net/l2tp/l2tp_ip6.c b/net/l2tp/l2tp_ip6.c index ea232f338dcb..2d26d10b6612 100644 --- a/net/l2tp/l2tp_ip6.c +++ b/net/l2tp/l2tp_ip6.c @@ -678,7 +678,7 @@ do_confirm: } static int l2tp_ip6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { struct ipv6_pinfo *np = inet6_sk(sk); DECLARE_SOCKADDR(struct sockaddr_l2tpip6 *, lsa, msg->msg_name); @@ -690,7 +690,7 @@ static int l2tp_ip6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, goto out; if (flags & MSG_ERRQUEUE) - return ipv6_recv_error(sk, msg, len, addr_len); + return ipv6_recv_error(sk, msg, len); skb = skb_recv_datagram(sk, flags, &err); if (!skb) @@ -718,7 +718,7 @@ static int l2tp_ip6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, lsa->l2tp_conn_id = 0; if (ipv6_addr_type(&lsa->l2tp_addr) & IPV6_ADDR_LINKLOCAL) lsa->l2tp_scope_id = inet6_iif(skb); - *addr_len = sizeof(*lsa); + msg->msg_namelen = sizeof(*lsa); } if (np->rxopt.all) diff --git a/net/llc/af_llc.c b/net/llc/af_llc.c index 5958a80fe14c..1863cacc63a8 100644 --- a/net/llc/af_llc.c +++ b/net/llc/af_llc.c @@ -317,6 +317,7 @@ static int llc_ui_autobind(struct socket *sock, struct sockaddr_llc *addr) /* assign new connection to its SAP */ llc_sap_add_socket(sap, sk); sock_reset_flag(sk, SOCK_ZAPPED); + llc_sap_put(sap); rc = 0; out: dev_put(dev); diff --git a/net/llc/llc_conn.c b/net/llc/llc_conn.c index 5c0ac243b248..b4d732baafb8 100644 --- a/net/llc/llc_conn.c +++ b/net/llc/llc_conn.c @@ -773,7 +773,6 @@ static struct sock *llc_create_incoming_sock(struct sock *sk, newllc->dev = dev; dev_hold(dev); llc_sap_add_socket(llc->sap, newsk); - llc_sap_hold(llc->sap); out: return newsk; } diff --git a/net/mac80211/main.c b/net/mac80211/main.c index eefa6f7e899b..655a8aec7738 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -1575,7 +1575,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) sband = kmemdup(sband, sizeof(*sband), GFP_KERNEL); if (!sband) { result = -ENOMEM; - goto fail_rate; + goto fail_band; } wiphy_dbg(hw->wiphy, "copying sband (band %d) due to VHT EXT NSS BW flag\n", @@ -1642,6 +1642,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) #endif wiphy_unregister(local->hw.wiphy); fail_wiphy_register: + fail_band: rtnl_lock(); rate_control_deinitialize(local); ieee80211_remove_interfaces(local); diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 98f0a275b60c..c125871adb62 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2578,6 +2578,18 @@ static u16 ieee80211_store_ack_skb(struct ieee80211_local *local, return info_id; } +static void ieee80211_remove_ack_skb(struct ieee80211_local *local, u16 info_id) +{ + struct sk_buff *ack_skb; + unsigned long flags; + + spin_lock_irqsave(&local->ack_status_lock, flags); + ack_skb = idr_remove(&local->ack_status_frames, info_id); + spin_unlock_irqrestore(&local->ack_status_lock, flags); + + kfree_skb(ack_skb); +} + /** * ieee80211_build_hdr - build 802.11 header in the given frame * @sdata: virtual interface to build the header for @@ -2934,7 +2946,8 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, if (ieee80211_skb_resize(sdata, skb, head_need, ENCRYPT_DATA)) { ieee80211_free_txskb(&local->hw, skb); skb = NULL; - return ERR_PTR(-ENOMEM); + ret = -ENOMEM; + goto free; } } @@ -3002,6 +3015,8 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, return skb; free: + if (info_id) + ieee80211_remove_ack_skb(local, info_id); kfree_skb(skb); return ERR_PTR(ret); } diff --git a/net/mac802154/iface.c b/net/mac802154/iface.c index 000be60d9580..b823720630e7 100644 --- a/net/mac802154/iface.c +++ b/net/mac802154/iface.c @@ -703,7 +703,7 @@ void ieee802154_remove_interfaces(struct ieee802154_local *local) mutex_lock(&local->iflist_mtx); list_for_each_entry_safe(sdata, tmp, &local->interfaces, list) { - list_del(&sdata->list); + list_del_rcu(&sdata->list); unregister_netdevice(sdata->dev); } diff --git a/net/mac802154/scan.c b/net/mac802154/scan.c index a6dab3cc3ad8..0eef3b4f5b9b 100644 --- a/net/mac802154/scan.c +++ b/net/mac802154/scan.c @@ -594,6 +594,7 @@ int mac802154_perform_association(struct ieee802154_sub_if_data *sdata, "Negative ASSOC RESP received from %8phC: %s\n", &ceaddr, local->assoc_status == IEEE802154_PAN_AT_CAPACITY ? "PAN at capacity" : "access denied"); + goto clear_assoc; } ret = 0; diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index b82b2a409487..72a909bc0fca 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -2206,7 +2206,7 @@ static unsigned int mptcp_inq_hint(const struct sock *sk) } static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { struct mptcp_sock *msk = mptcp_sk(sk); struct scm_timestamping_internal tss; @@ -2216,7 +2216,7 @@ static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, /* MSG_ERRQUEUE is really a no-op till we support IP_RECVERR */ if (unlikely(flags & MSG_ERRQUEUE)) - return inet_recv_error(sk, msg, len, addr_len); + return inet_recv_error(sk, msg, len); lock_sock(sk); if (unlikely(sk->sk_state == TCP_LISTEN)) { diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile index e43e20f529f8..6bfc250e474f 100644 --- a/net/netfilter/Makefile +++ b/net/netfilter/Makefile @@ -141,6 +141,7 @@ obj-$(CONFIG_NFT_FWD_NETDEV) += nft_fwd_netdev.o # flow table infrastructure obj-$(CONFIG_NF_FLOW_TABLE) += nf_flow_table.o nf_flow_table-objs := nf_flow_table_core.o nf_flow_table_ip.o \ + nf_flow_table_path.o \ nf_flow_table_offload.o nf_flow_table_xdp.o nf_flow_table-$(CONFIG_NF_FLOW_TABLE_PROCFS) += nf_flow_table_procfs.o ifeq ($(CONFIG_NF_FLOW_TABLE),m) diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c index 94157104d85a..f51a1af31513 100644 --- a/net/netfilter/ipset/ip_set_core.c +++ b/net/netfilter/ipset/ip_set_core.c @@ -351,8 +351,8 @@ ip_set_init_comment(struct ip_set *set, struct ip_set_comment *comment, if (unlikely(c)) { set->ext_size -= sizeof(*c) + strlen(c->str) + 1; - kfree_rcu(c, rcu); rcu_assign_pointer(comment->c, NULL); + kfree_rcu(c, rcu); } if (!len) return; @@ -393,8 +393,8 @@ ip_set_comment_free(struct ip_set *set, void *ptr) if (unlikely(!c)) return; set->ext_size -= sizeof(*c) + strlen(c->str) + 1; - kfree_rcu(c, rcu); rcu_assign_pointer(comment->c, NULL); + kfree_rcu(c, rcu); } typedef void (*destroyer)(struct ip_set *, void *); diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h index 4e56269efef2..0426d012185d 100644 --- a/net/netfilter/ipset/ip_set_hash_gen.h +++ b/net/netfilter/ipset/ip_set_hash_gen.h @@ -386,8 +386,9 @@ static void mtype_ext_cleanup(struct ip_set *set, struct hbucket *n) { int i; + u8 pos = smp_load_acquire(&n->pos); - for (i = 0; i < n->pos; i++) + for (i = 0; i < pos; i++) if (test_bit(i, n->used)) ip_set_ext_destroy(set, ahash_data(n, i, set->dsize)); } @@ -490,7 +491,7 @@ mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r) #ifdef IP_SET_HASH_WITH_NETS u8 k; #endif - u8 htable_bits = t->htable_bits; + u8 pos, htable_bits = t->htable_bits; spin_lock_bh(&t->hregion[r].lock); for (i = ahash_bucket_start(r, htable_bits); @@ -498,7 +499,8 @@ mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r) n = __ipset_dereference(hbucket(t, i)); if (!n) continue; - for (j = 0, d = 0; j < n->pos; j++) { + pos = smp_load_acquire(&n->pos); + for (j = 0, d = 0; j < pos; j++) { if (!test_bit(j, n->used)) { d++; continue; @@ -534,7 +536,7 @@ mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r) /* Still try to delete expired elements. */ continue; tmp->size = n->size - AHASH_INIT_SIZE; - for (j = 0, d = 0; j < n->pos; j++) { + for (j = 0, d = 0; j < pos; j++) { if (!test_bit(j, n->used)) continue; data = ahash_data(n, j, dsize); @@ -604,7 +606,7 @@ mtype_cancel_gc(struct ip_set *set) struct htype *h = set->data; if (SET_WITH_TIMEOUT(set)) - cancel_delayed_work_sync(&h->gc.dwork); + disable_delayed_work_sync(&h->gc.dwork); } static int @@ -623,7 +625,7 @@ mtype_resize(struct ip_set *set, bool retried) { struct htype *h = set->data; struct htable *t, *orig; - u8 htable_bits; + u8 pos, htable_bits; size_t hsize, dsize = set->dsize; #ifdef IP_SET_HASH_WITH_NETS u8 flags; @@ -685,8 +687,9 @@ retry: n = __ipset_dereference(hbucket(orig, i)); if (!n) continue; - for (j = 0; j < n->pos; j++) { - if (!test_bit(j, n->used)) + pos = smp_load_acquire(&n->pos); + for (j = 0; j < pos; j++) { + if (!test_bit_acquire(j, n->used)) continue; data = ahash_data(n, j, dsize); if (SET_ELEM_EXPIRED(set, data)) @@ -809,9 +812,10 @@ mtype_ext_size(struct ip_set *set, u32 *elements, size_t *ext_size) { struct htype *h = set->data; const struct htable *t; - u32 i, j, r; struct hbucket *n; struct mtype_elem *data; + u32 i, j, r; + u8 pos; t = rcu_dereference_bh(h->table); for (r = 0; r < ahash_numof_locks(t->htable_bits); r++) { @@ -820,8 +824,9 @@ mtype_ext_size(struct ip_set *set, u32 *elements, size_t *ext_size) n = rcu_dereference_bh(hbucket(t, i)); if (!n) continue; - for (j = 0; j < n->pos; j++) { - if (!test_bit(j, n->used)) + pos = smp_load_acquire(&n->pos); + for (j = 0; j < pos; j++) { + if (!test_bit_acquire(j, n->used)) continue; data = ahash_data(n, j, set->dsize); if (!SET_ELEM_EXPIRED(set, data)) @@ -848,6 +853,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, bool flag_exist = flags & IPSET_FLAG_EXIST; bool deleted = false, forceadd = false, reuse = false; u32 r, key, multi = 0, elements, maxelem; + u8 npos = 0; rcu_read_lock_bh(); t = rcu_dereference_bh(h->table); @@ -889,7 +895,8 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, ext_size(AHASH_INIT_SIZE, set->dsize); goto copy_elem; } - for (i = 0; i < n->pos; i++) { + npos = smp_load_acquire(&n->pos); + for (i = 0; i < npos; i++) { if (!test_bit(i, n->used)) { /* Reuse first deleted entry */ if (j == -1) { @@ -933,7 +940,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, if (elements >= maxelem) goto set_full; /* Create a new slot */ - if (n->pos >= n->size) { + if (npos >= n->size) { #ifdef IP_SET_HASH_WITH_MULTI if (h->bucketsize >= AHASH_MAX_TUNED) goto set_full; @@ -962,7 +969,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext, } copy_elem: - j = n->pos++; + j = npos++; data = ahash_data(n, j, set->dsize); copy_data: t->hregion[r].elements++; @@ -985,6 +992,8 @@ overwrite_extensions: if (SET_WITH_TIMEOUT(set)) ip_set_timeout_set(ext_timeout(data, set), ext->timeout); smp_mb__before_atomic(); + /* Ensure all data writes are visible before updating position */ + smp_store_release(&n->pos, npos); set_bit(j, n->used); if (old != ERR_PTR(-ENOENT)) { rcu_assign_pointer(hbucket(t, key), n); @@ -1043,6 +1052,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext, int i, j, k, r, ret = -IPSET_ERR_EXIST; u32 key, multi = 0; size_t dsize = set->dsize; + u8 pos; /* Userspace add and resize is excluded by the mutex. * Kernespace add does not trigger resize. @@ -1058,7 +1068,8 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext, n = rcu_dereference_bh(hbucket(t, key)); if (!n) goto out; - for (i = 0, k = 0; i < n->pos; i++) { + pos = smp_load_acquire(&n->pos); + for (i = 0, k = 0; i < pos; i++) { if (!test_bit(i, n->used)) { k++; continue; @@ -1072,8 +1083,8 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext, ret = 0; clear_bit(i, n->used); smp_mb__after_atomic(); - if (i + 1 == n->pos) - n->pos--; + if (i + 1 == pos) + smp_store_release(&n->pos, --pos); t->hregion[r].elements--; #ifdef IP_SET_HASH_WITH_NETS for (j = 0; j < IPSET_NET_COUNT; j++) @@ -1095,11 +1106,11 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext, x->flags = flags; } } - for (; i < n->pos; i++) { + for (; i < pos; i++) { if (!test_bit(i, n->used)) k++; } - if (k == n->pos) { + if (k == pos) { t->hregion[r].ext_size -= ext_size(n->size, dsize); rcu_assign_pointer(hbucket(t, key), NULL); kfree_rcu(n, rcu); @@ -1110,7 +1121,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext, if (!tmp) goto out; tmp->size = n->size - AHASH_INIT_SIZE; - for (j = 0, k = 0; j < n->pos; j++) { + for (j = 0, k = 0; j < pos; j++) { if (!test_bit(j, n->used)) continue; data = ahash_data(n, j, dsize); @@ -1171,6 +1182,7 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d, int ret, i, j = 0; #endif u32 key, multi = 0; + u8 pos; pr_debug("test by nets\n"); for (; j < NLEN && h->nets[j].cidr[0] && !multi; j++) { @@ -1188,8 +1200,9 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d, n = rcu_dereference_bh(hbucket(t, key)); if (!n) continue; - for (i = 0; i < n->pos; i++) { - if (!test_bit(i, n->used)) + pos = smp_load_acquire(&n->pos); + for (i = 0; i < pos; i++) { + if (!test_bit_acquire(i, n->used)) continue; data = ahash_data(n, i, set->dsize); if (!mtype_data_equal(data, d, &multi)) @@ -1222,6 +1235,7 @@ mtype_test(struct ip_set *set, void *value, const struct ip_set_ext *ext, struct mtype_elem *data; int i, ret = 0; u32 key, multi = 0; + u8 pos; rcu_read_lock_bh(); t = rcu_dereference_bh(h->table); @@ -1244,8 +1258,9 @@ mtype_test(struct ip_set *set, void *value, const struct ip_set_ext *ext, ret = 0; goto out; } - for (i = 0; i < n->pos; i++) { - if (!test_bit(i, n->used)) + pos = smp_load_acquire(&n->pos); + for (i = 0; i < pos; i++) { + if (!test_bit_acquire(i, n->used)) continue; data = ahash_data(n, i, set->dsize); if (!mtype_data_equal(data, d, &multi)) @@ -1361,6 +1376,7 @@ mtype_list(const struct ip_set *set, /* We assume that one hash bucket fills into one page */ void *incomplete; int i, ret = 0; + u8 pos; atd = nla_nest_start(skb, IPSET_ATTR_ADT); if (!atd) @@ -1379,8 +1395,9 @@ mtype_list(const struct ip_set *set, cb->args[IPSET_CB_ARG0], t, n); if (!n) continue; - for (i = 0; i < n->pos; i++) { - if (!test_bit(i, n->used)) + pos = smp_load_acquire(&n->pos); + for (i = 0; i < pos; i++) { + if (!test_bit_acquire(i, n->used)) continue; e = ahash_data(n, i, set->dsize); if (SET_ELEM_EXPIRED(set, e)) diff --git a/net/netfilter/ipvs/ip_vs_app.c b/net/netfilter/ipvs/ip_vs_app.c index fdacbc3c15be..f9132b359f0c 100644 --- a/net/netfilter/ipvs/ip_vs_app.c +++ b/net/netfilter/ipvs/ip_vs_app.c @@ -362,14 +362,13 @@ static inline int app_tcp_pkt_out(struct ip_vs_conn *cp, struct sk_buff *skb, struct ip_vs_iphdr *ipvsh) { int diff; - const unsigned int tcp_offset = ip_hdrlen(skb); struct tcphdr *th; __u32 seq; - if (skb_ensure_writable(skb, tcp_offset + sizeof(*th))) + if (skb_ensure_writable(skb, ipvsh->len + sizeof(*th))) return 0; - th = (struct tcphdr *)(skb_network_header(skb) + tcp_offset); + th = (struct tcphdr *)(skb_network_header(skb) + ipvsh->len); /* * Remember seq number in case this pkt gets resized @@ -439,14 +438,13 @@ static inline int app_tcp_pkt_in(struct ip_vs_conn *cp, struct sk_buff *skb, struct ip_vs_iphdr *ipvsh) { int diff; - const unsigned int tcp_offset = ip_hdrlen(skb); struct tcphdr *th; __u32 seq; - if (skb_ensure_writable(skb, tcp_offset + sizeof(*th))) + if (skb_ensure_writable(skb, ipvsh->len + sizeof(*th))) return 0; - th = (struct tcphdr *)(skb_network_header(skb) + tcp_offset); + th = (struct tcphdr *)(skb_network_header(skb) + ipvsh->len); /* * Remember seq number in case this pkt gets resized diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index 37ebb0cb62b8..bc8ff5f15702 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -996,8 +996,8 @@ ip_vs_conn_new(const struct ip_vs_conn_param *p, int dest_af, cp->app = NULL; cp->app_data = NULL; /* reset struct ip_vs_seq */ - cp->in_seq.delta = 0; - cp->out_seq.delta = 0; + memset(&cp->in_seq, 0, sizeof(cp->in_seq)); + memset(&cp->out_seq, 0, sizeof(cp->out_seq)); atomic_inc(&ipvs->conn_count); if (flags & IP_VS_CONN_F_NO_CPORT) diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index 5ea7ab8bf4dc..90619453cb6f 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -220,10 +220,10 @@ ip_vs_conn_stats(struct ip_vs_conn *cp, struct ip_vs_service *svc) static inline void ip_vs_set_state(struct ip_vs_conn *cp, int direction, const struct sk_buff *skb, - struct ip_vs_proto_data *pd) + struct ip_vs_proto_data *pd, unsigned int iph_len) { if (likely(pd->pp->state_transition)) - pd->pp->state_transition(cp, direction, skb, pd); + pd->pp->state_transition(cp, direction, skb, pd, iph_len); } static inline int @@ -625,7 +625,7 @@ int ip_vs_leave(struct ip_vs_service *svc, struct sk_buff *skb, ip_vs_in_stats(cp, skb); /* set state */ - ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd); + ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd, iph->len); /* transmit the first SYN packet */ ret = cp->packet_xmit(skb, cp, pd->pp, iph); @@ -1041,8 +1041,7 @@ static int ip_vs_out_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, snet.in6 = ciph.saddr.in6; offset = ciph.len; return handle_response_icmp(AF_INET6, skb, &snet, ciph.protocol, cp, - pp, offset, sizeof(struct ipv6hdr), - hooknum); + pp, offset, ipvsh->len, hooknum); } #endif @@ -1306,7 +1305,7 @@ handle_response(int af, struct sk_buff *skb, struct ip_vs_proto_data *pd, after_nat: ip_vs_out_stats(cp, skb); - ip_vs_set_state(cp, IP_VS_DIR_OUTPUT, skb, pd); + ip_vs_set_state(cp, IP_VS_DIR_OUTPUT, skb, pd, iph->len); skb->ipvs_property = 1; if (!(cp->flags & IP_VS_CONN_F_NFCT)) ip_vs_notrack(skb); @@ -1589,6 +1588,8 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, bool tunnel, new_cp = false; union nf_inet_addr *raddr; char *outer_proto = "IPIP"; + unsigned int hlen_ipip; + int ulen = 0; *related = 1; @@ -1625,9 +1626,10 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, /* Now find the contained IP header */ offset += sizeof(_icmph); cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph); - if (cih == NULL) + if (!(cih && cih->version == 4 && cih->ihl >= 5)) return NF_ACCEPT; /* The packet looks wrong, ignore */ raddr = (union nf_inet_addr *)&cih->daddr; + hlen_ipip = cih->ihl * 4; /* Special case for errors for IPIP/UDP/GRE tunnel packets */ tunnel = false; @@ -1643,9 +1645,9 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, /* Only for known tunnel */ if (!dest || dest->tun_type != IP_VS_CONN_F_TUNNEL_TYPE_IPIP) return NF_ACCEPT; - offset += cih->ihl * 4; + offset += hlen_ipip; cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph); - if (cih == NULL) + if (!(cih && cih->version == 4 && cih->ihl >= 5)) return NF_ACCEPT; /* The packet looks wrong, ignore */ tunnel = true; } else if ((cih->protocol == IPPROTO_UDP || /* Can be UDP encap */ @@ -1653,12 +1655,11 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, /* Error for our tunnel must arrive at LOCAL_IN */ (skb_rtable(skb)->rt_flags & RTCF_LOCAL)) { __u8 iproto; - int ulen; /* Non-first fragment has no UDP/GRE header */ if (unlikely(cih->frag_off & htons(IP_OFFSET))) return NF_ACCEPT; - offset2 = offset + cih->ihl * 4; + offset2 = offset + hlen_ipip; if (cih->protocol == IPPROTO_UDP) { ulen = ipvs_udp_decap(ipvs, skb, offset2, AF_INET, raddr, &iproto); @@ -1727,6 +1728,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, } if (tunnel) { + unsigned int hlen_orig = cih->ihl * 4; __be32 info = ic->un.gateway; __u8 type = ic->type; __u8 code = ic->code; @@ -1743,6 +1745,9 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, goto ignore_tunnel; offset2 -= ihl + sizeof(_icmph); skb_reset_network_header(skb); + /* Ensure the IP header is present in headroom */ + if (!pskb_may_pull(skb, hlen_ipip)) + goto ignore_tunnel; IP_VS_DBG(12, "ICMP for %s %pI4->%pI4: mtu=%u\n", outer_proto, &ip_hdr(skb)->saddr, &ip_hdr(skb)->daddr, mtu); @@ -1758,8 +1763,8 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, if (dest_dst) mtu = dst_mtu(dest_dst->dst_cache); } - if (mtu > 68 + sizeof(struct iphdr)) - mtu -= sizeof(struct iphdr); + if (mtu > 68 + hlen_ipip + ulen) + mtu -= hlen_ipip + ulen; info = htonl(mtu); } /* Strip outer IP, ICMP and IPIP/UDP/GRE, go to IP header of @@ -1768,6 +1773,9 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, if (pskb_pull(skb, offset2) == NULL) goto ignore_tunnel; skb_reset_network_header(skb); + /* Ensure the IP header is present in headroom */ + if (!pskb_may_pull(skb, hlen_orig)) + goto ignore_tunnel; IP_VS_DBG(12, "Sending ICMP for %pI4->%pI4: t=%u, c=%u, i=%u\n", &ip_hdr(skb)->saddr, &ip_hdr(skb)->daddr, type, code, ntohl(info)); @@ -2055,7 +2063,7 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state IP_VS_DBG_PKT(11, af, pp, skb, iph.off, "Incoming packet"); ip_vs_in_stats(cp, skb); - ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd); + ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd, iph.len); if (cp->packet_xmit) ret = cp->packet_xmit(skb, cp, pp, &iph); /* do not touch skb anymore */ diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c index 63c78a1f3918..c67317be17df 100644 --- a/net/netfilter/ipvs/ip_vs_proto_sctp.c +++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c @@ -372,20 +372,15 @@ static const char *sctp_state_name(int state) static inline void set_sctp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp, - int direction, const struct sk_buff *skb) + int direction, const struct sk_buff *skb, + unsigned int iph_len) { struct sctp_chunkhdr _sctpch, *sch; unsigned char chunk_type; int event, next_state; - int ihl, cofs; + int cofs; -#ifdef CONFIG_IP_VS_IPV6 - ihl = cp->af == AF_INET ? ip_hdrlen(skb) : sizeof(struct ipv6hdr); -#else - ihl = ip_hdrlen(skb); -#endif - - cofs = ihl + sizeof(struct sctphdr); + cofs = iph_len + sizeof(struct sctphdr); sch = skb_header_pointer(skb, cofs, sizeof(_sctpch), &_sctpch); if (sch == NULL) return; @@ -468,10 +463,11 @@ set_sctp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp, static void sctp_state_transition(struct ip_vs_conn *cp, int direction, - const struct sk_buff *skb, struct ip_vs_proto_data *pd) + const struct sk_buff *skb, struct ip_vs_proto_data *pd, + unsigned int iph_len) { spin_lock_bh(&cp->lock); - set_sctp_state(pd, cp, direction, skb); + set_sctp_state(pd, cp, direction, skb, iph_len); spin_unlock_bh(&cp->lock); } diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c index ede4fa3b63f5..b382810156b2 100644 --- a/net/netfilter/ipvs/ip_vs_proto_tcp.c +++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c @@ -580,17 +580,12 @@ set_tcp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp, static void tcp_state_transition(struct ip_vs_conn *cp, int direction, const struct sk_buff *skb, - struct ip_vs_proto_data *pd) + struct ip_vs_proto_data *pd, + unsigned int iph_len) { struct tcphdr _tcph, *th; -#ifdef CONFIG_IP_VS_IPV6 - int ihl = cp->af == AF_INET ? ip_hdrlen(skb) : sizeof(struct ipv6hdr); -#else - int ihl = ip_hdrlen(skb); -#endif - - th = skb_header_pointer(skb, ihl, sizeof(_tcph), &_tcph); + th = skb_header_pointer(skb, iph_len, sizeof(_tcph), &_tcph); if (th == NULL) return; diff --git a/net/netfilter/ipvs/ip_vs_proto_udp.c b/net/netfilter/ipvs/ip_vs_proto_udp.c index ffbebda547fc..dbd4155bb075 100644 --- a/net/netfilter/ipvs/ip_vs_proto_udp.c +++ b/net/netfilter/ipvs/ip_vs_proto_udp.c @@ -445,7 +445,8 @@ static const char * udp_state_name(int state) static void udp_state_transition(struct ip_vs_conn *cp, int direction, const struct sk_buff *skb, - struct ip_vs_proto_data *pd) + struct ip_vs_proto_data *pd, + unsigned int iph_len) { if (unlikely(!pd)) { pr_err("UDP no ns data\n"); diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index fc254d1f59de..f564b4197cee 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -733,13 +733,11 @@ int ip_vs_bypass_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh) { - struct iphdr *iph = ip_hdr(skb); - - if (__ip_vs_get_out_rt(cp->ipvs, cp->af, skb, NULL, iph->daddr, + if (__ip_vs_get_out_rt(cp->ipvs, cp->af, skb, NULL, ip_hdr(skb)->daddr, IP_VS_RT_MODE_NON_LOCAL, NULL, ipvsh) < 0) goto tx_error; - ip_send_check(iph); + ip_send_check(ip_hdr(skb)); /* Another hack: avoid icmp_send in ip_fragment */ skb->ignore_df = 1; diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index 14e62b3263cd..aca365f9f0ae 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -179,17 +179,16 @@ static int __nf_conncount_add(struct net *net, return -ENOENT; if (ct && nf_ct_is_confirmed(ct)) { - /* local connections are confirmed in postrouting so confirmation - * might have happened before hitting connlimit + /* Connection is confirmed but might still be in the setup phase. + * Only skip the tracking if it is fully assured. This guarantees + * that setup packets or retransmissions are properly counted and + * deduplicated. */ - if (skb->skb_iif != LOOPBACK_IFINDEX) { + if (test_bit(IPS_ASSURED_BIT, &ct->status)) { err = -EEXIST; goto out_put; } - /* this is likely a local connection, skip optimization to avoid - * adding duplicates from a 'packet train' - */ goto check_connections; } @@ -208,8 +207,8 @@ check_connections: /* Not found, but might be about to be confirmed */ if (PTR_ERR(found) == -EAGAIN) { if (nf_ct_tuple_equal(&conn->tuple, &tuple) && - nf_ct_zone_id(&conn->zone, conn->zone.dir) == - nf_ct_zone_id(zone, zone->dir)) + nf_ct_zone_id(&conn->zone, IP_CT_DIR_ORIGINAL) == + nf_ct_zone_id(zone, IP_CT_DIR_ORIGINAL)) goto out_put; /* already exists */ } else { collect++; @@ -220,7 +219,7 @@ check_connections: found_ct = nf_ct_tuplehash_to_ctrack(found); if (nf_ct_tuple_equal(&conn->tuple, &tuple) && - nf_ct_zone_equal(found_ct, zone, zone->dir)) { + nf_ct_zone_equal(found_ct, zone, IP_CT_DIR_ORIGINAL)) { /* * We should not see tuples twice unless someone hooks * this into a table without "-p tcp --syn". @@ -499,7 +498,7 @@ count_tree(struct net *net, hash = jhash2(key, data->keylen, conncount_rnd) % CONNCOUNT_SLOTS; root = &data->root[hash]; - parent = rcu_dereference_raw(root->rb_node); + parent = rcu_dereference(root->rb_node); while (parent) { int diff; @@ -507,9 +506,9 @@ count_tree(struct net *net, diff = key_diff(key, rbconn->key, data->keylen); if (diff < 0) { - parent = rcu_dereference_raw(parent->rb_left); + parent = rcu_dereference(parent->rb_left); } else if (diff > 0) { - parent = rcu_dereference_raw(parent->rb_right); + parent = rcu_dereference(parent->rb_right); } else { int ret; diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c index 344f88295976..a877f9ad1cf2 100644 --- a/net/netfilter/nf_conntrack_core.c +++ b/net/netfilter/nf_conntrack_core.c @@ -565,9 +565,23 @@ static void destroy_gre_conntrack(struct nf_conn *ct) { #ifdef CONFIG_NF_CT_PROTO_GRE struct nf_conn *master = ct->master; + struct nf_conn_help *help; + + if (!master) + return; + + help = nfct_help(master); + if (help) { + struct nf_conntrack_helper *helper; + + rcu_read_lock(); + helper = rcu_dereference(help->helper); + /* Only pptp helper has a destroy callback. */ + if (helper && helper->destroy) + nf_ct_gre_keymap_destroy(master); - if (master) - nf_ct_gre_keymap_destroy(master); + rcu_read_unlock(); + } #endif } @@ -836,33 +850,6 @@ static void __nf_conntrack_hash_insert(struct nf_conn *ct, &nf_conntrack_hash[reply_hash]); } -static bool nf_ct_ext_valid_pre(const struct nf_ct_ext *ext) -{ - /* if ext->gen_id is not equal to nf_conntrack_ext_genid, some extensions - * may contain stale pointers to e.g. helper that has been removed. - * - * The helper can't clear this because the nf_conn object isn't in - * any hash and synchronize_rcu() isn't enough because associated skb - * might sit in a queue. - */ - return !ext || ext->gen_id == atomic_read(&nf_conntrack_ext_genid); -} - -static bool nf_ct_ext_valid_post(struct nf_ct_ext *ext) -{ - if (!ext) - return true; - - if (ext->gen_id != atomic_read(&nf_conntrack_ext_genid)) - return false; - - /* inserted into conntrack table, nf_ct_iterate_cleanup() - * will find it. Disable nf_ct_ext_find() id check. - */ - WRITE_ONCE(ext->gen_id, 0); - return true; -} - int nf_conntrack_hash_check_insert(struct nf_conn *ct) { @@ -878,9 +865,6 @@ nf_conntrack_hash_check_insert(struct nf_conn *ct) zone = nf_ct_zone(ct); - if (!nf_ct_ext_valid_pre(ct->ext)) - return -EAGAIN; - local_bh_disable(); do { sequence = read_seqcount_begin(&nf_conntrack_generation); @@ -914,18 +898,6 @@ nf_conntrack_hash_check_insert(struct nf_conn *ct) goto chaintoolong; } - /* If genid has changed, we can't insert anymore because ct - * extensions could have stale pointers and nf_ct_iterate_destroy - * might have completed its table scan already. - * - * Increment of the ext genid right after this check is fine: - * nf_ct_iterate_destroy blocks until locks are released. - */ - if (!nf_ct_ext_valid_post(ct->ext)) { - err = -EAGAIN; - goto out; - } - smp_wmb(); /* The caller holds a reference to this object */ refcount_set(&ct->ct_general.use, 2); @@ -1253,11 +1225,6 @@ __nf_conntrack_confirm(struct sk_buff *skb) return NF_DROP; } - if (!nf_ct_ext_valid_pre(ct->ext)) { - NF_CT_STAT_INC(net, insert_failed); - goto dying; - } - /* We have to check the DYING flag after unlink to prevent * a race against nf_ct_get_next_corpse() possibly called from * user context, else we insert an already 'dead' hash, blocking @@ -1320,16 +1287,6 @@ chaintoolong: nf_conntrack_double_unlock(hash, reply_hash); local_bh_enable(); - /* ext area is still valid (rcu read lock is held, - * but will go out of scope soon, we need to remove - * this conntrack again. - */ - if (!nf_ct_ext_valid_post(ct->ext)) { - nf_ct_kill(ct); - NF_CT_STAT_INC_ATOMIC(net, drop); - return NF_DROP; - } - help = nfct_help(ct); if (help && help->helper) nf_conntrack_event_cache(IPCT_HELPER, ct); @@ -2211,6 +2168,7 @@ static int nf_confirm_cthelper(struct sk_buff *skb, struct nf_conn *ct, { const struct nf_conntrack_helper *helper; const struct nf_conn_help *help; + unsigned int helper_flags; int protoff; help = nfct_help(ct); @@ -2221,7 +2179,8 @@ static int nf_confirm_cthelper(struct sk_buff *skb, struct nf_conn *ct, if (!helper) return NF_ACCEPT; - if (!(helper->flags & NF_CT_HELPER_F_USERSPACE)) + helper_flags = READ_ONCE(helper->flags); + if (!(helper_flags & NF_CT_HELPER_F_USERSPACE)) return NF_ACCEPT; switch (nf_ct_l3num(ct)) { @@ -2434,13 +2393,11 @@ nf_ct_iterate_destroy(int (*iter)(struct nf_conn *i, void *data), void *data) */ synchronize_net(); - nf_ct_ext_bump_genid(); iter_data.data = data; nf_ct_iterate_cleanup(iter, &iter_data); /* Another cpu might be in a rcu read section with - * rcu protected pointer cleared in iter callback - * or hidden via nf_ct_ext_bump_genid() above. + * rcu protected pointer cleared in iter callback. * * Wait until those are done. */ diff --git a/net/netfilter/nf_conntrack_ecache.c b/net/netfilter/nf_conntrack_ecache.c index 9df159448b89..cc8d8e85169f 100644 --- a/net/netfilter/nf_conntrack_ecache.c +++ b/net/netfilter/nf_conntrack_ecache.c @@ -77,7 +77,7 @@ next: hlist_nulls_del_rcu(&ct->tuplehash[IP_CT_DIR_ORIGINAL].hnnode); hlist_nulls_add_head(&ct->tuplehash[IP_CT_DIR_REPLY].hnnode, &evicted_list); - if (time_after(stop, jiffies)) { + if (time_after(jiffies, stop)) { ret = STATE_RESTART; break; } diff --git a/net/netfilter/nf_conntrack_extend.c b/net/netfilter/nf_conntrack_extend.c index dd62cc12e775..0da105e1ded9 100644 --- a/net/netfilter/nf_conntrack_extend.c +++ b/net/netfilter/nf_conntrack_extend.c @@ -27,8 +27,6 @@ #define NF_CT_EXT_PREALLOC 128u /* conntrack events are on by default */ -atomic_t nf_conntrack_ext_genid __read_mostly = ATOMIC_INIT(1); - static const u8 nf_ct_ext_type_len[NF_CT_EXT_NUM] = { [NF_CT_EXT_HELPER] = sizeof(struct nf_conn_help), #if IS_ENABLED(CONFIG_NF_NAT) @@ -118,10 +116,8 @@ void *nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp) if (!new) return NULL; - if (!ct->ext) { + if (!ct->ext) memset(new->offset, 0, sizeof(new->offset)); - new->gen_id = atomic_read(&nf_conntrack_ext_genid); - } new->offset[id] = newoff; new->len = newlen; @@ -131,29 +127,3 @@ void *nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp) return (void *)new + newoff; } EXPORT_SYMBOL(nf_ct_ext_add); - -/* Use nf_ct_ext_find wrapper. This is only useful for unconfirmed entries. */ -void *__nf_ct_ext_find(const struct nf_ct_ext *ext, u8 id) -{ - unsigned int gen_id = atomic_read(&nf_conntrack_ext_genid); - unsigned int this_id = READ_ONCE(ext->gen_id); - - if (!__nf_ct_ext_exist(ext, id)) - return NULL; - - if (this_id == 0 || ext->gen_id == gen_id) - return (void *)ext + ext->offset[id]; - - return NULL; -} -EXPORT_SYMBOL(__nf_ct_ext_find); - -void nf_ct_ext_bump_genid(void) -{ - unsigned int value = atomic_inc_return(&nf_conntrack_ext_genid); - - if (value == UINT_MAX) - atomic_set(&nf_conntrack_ext_genid, 1); - - msleep(HZ); -} diff --git a/net/netfilter/nf_conntrack_irc.c b/net/netfilter/nf_conntrack_irc.c index 0f50ea92ced9..454532838870 100644 --- a/net/netfilter/nf_conntrack_irc.c +++ b/net/netfilter/nf_conntrack_irc.c @@ -64,7 +64,7 @@ static const char *const dccprotos[] = { /* tries to get the ip_addr and port out of a dcc command * return value: -1 on failure, 0 on success * data pointer to first byte of DCC command data - * data_end pointer to last byte of dcc command data + * data_end one past end of data * ip returns parsed ip of dcc command * port returns parsed port of dcc command * ad_beg_p returns pointer to first byte of addr data @@ -82,10 +82,10 @@ static int parse_dcc(char *data, const char *data_end, __be32 *ip, /* Make sure we have a newline character within the packet boundaries * because simple_strtoul parses until the first invalid character. */ - for (tmp = data; tmp <= data_end; tmp++) + for (tmp = data; tmp < data_end; tmp++) if (*tmp == '\n') break; - if (tmp > data_end || *tmp != '\n') + if (tmp >= data_end || *tmp != '\n') return -1; *ad_beg_p = data; diff --git a/net/netfilter/nf_conntrack_seqadj.c b/net/netfilter/nf_conntrack_seqadj.c index 7ab2b25b57bc..b7e99f34dfce 100644 --- a/net/netfilter/nf_conntrack_seqadj.c +++ b/net/netfilter/nf_conntrack_seqadj.c @@ -17,12 +17,14 @@ int nf_ct_seqadj_init(struct nf_conn *ct, enum ip_conntrack_info ctinfo, if (off == 0) return 0; + spin_lock_bh(&ct->lock); set_bit(IPS_SEQ_ADJUST_BIT, &ct->status); seqadj = nfct_seqadj(ct); this_way = &seqadj->seq[dir]; this_way->offset_before = off; this_way->offset_after = off; + spin_unlock_bh(&ct->lock); return 0; } EXPORT_SYMBOL_GPL(nf_ct_seqadj_init); diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c new file mode 100644 index 000000000000..76d4ff145439 --- /dev/null +++ b/net/netfilter/nf_flow_table_path.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/netlink.h> +#include <linux/netfilter.h> +#include <linux/spinlock.h> +#include <linux/netfilter/nf_conntrack_common.h> +#include <linux/netfilter/nf_tables.h> +#include <net/ip.h> +#include <net/inet_dscp.h> +#include <net/netfilter/nf_tables.h> +#include <net/netfilter/nf_tables_core.h> +#include <net/netfilter/nf_conntrack_core.h> +#include <net/netfilter/nf_conntrack_extend.h> +#include <net/netfilter/nf_flow_table.h> + +static enum flow_offload_xmit_type nft_xmit_type(struct dst_entry *dst) +{ + if (dst_xfrm(dst)) + return FLOW_OFFLOAD_XMIT_XFRM; + + return FLOW_OFFLOAD_XMIT_NEIGH; +} + +static void nft_default_forward_path(struct nf_flow_route *route, + struct dst_entry *dst_cache, + enum ip_conntrack_dir dir) +{ + route->tuple[!dir].in.ifindex = dst_cache->dev->ifindex; + route->tuple[dir].dst = dst_cache; + route->tuple[dir].xmit_type = nft_xmit_type(dst_cache); +} + +static bool nft_is_valid_ether_device(const struct net_device *dev) +{ + if (!dev || (dev->flags & IFF_LOOPBACK) || dev->type != ARPHRD_ETHER || + dev->addr_len != ETH_ALEN || !is_valid_ether_addr(dev->dev_addr)) + return false; + + return true; +} + +static int nft_dev_fill_forward_path(const struct nf_flow_route *route, + const struct dst_entry *dst_cache, + const struct nf_conn *ct, + enum ip_conntrack_dir dir, u8 *ha, + struct net_device_path_stack *stack) +{ + const void *daddr = &ct->tuplehash[!dir].tuple.src.u3; + struct net_device *dev = dst_cache->dev; + struct neighbour *n; + u8 nud_state; + + if (!nft_is_valid_ether_device(dev)) { + eth_zero_addr(ha); + goto out; + } + + n = dst_neigh_lookup(dst_cache, daddr); + if (!n) + return -1; + + read_lock_bh(&n->lock); + nud_state = n->nud_state; + ether_addr_copy(ha, n->ha); + read_unlock_bh(&n->lock); + neigh_release(n); + + if (!(nud_state & NUD_VALID)) + return -1; + +out: + return dev_fill_forward_path(dev, ha, stack); +} + +struct nft_forward_info { + const struct net_device *indev; + const struct net_device *outdev; + const struct net_device *hw_outdev; + struct id { + __u16 id; + __be16 proto; + } encap[NF_FLOW_TABLE_ENCAP_MAX]; + u8 num_encaps; + u8 ingress_vlans; + u8 h_source[ETH_ALEN]; + u8 h_dest[ETH_ALEN]; + enum flow_offload_xmit_type xmit_type; +}; + +static void nft_dev_path_info(const struct net_device_path_stack *stack, + struct nft_forward_info *info, + unsigned char *ha, struct nf_flowtable *flowtable) +{ + const struct net_device_path *path; + int i; + + memcpy(info->h_dest, ha, ETH_ALEN); + + for (i = 0; i < stack->num_paths; i++) { + path = &stack->path[i]; + switch (path->type) { + case DEV_PATH_ETHERNET: + case DEV_PATH_DSA: + case DEV_PATH_VLAN: + case DEV_PATH_PPPOE: + info->indev = path->dev; + if (is_zero_ether_addr(info->h_source)) + memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN); + + if (path->type == DEV_PATH_ETHERNET) + break; + if (path->type == DEV_PATH_DSA) { + i = stack->num_paths; + break; + } + + /* DEV_PATH_VLAN and DEV_PATH_PPPOE */ + if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) { + info->indev = NULL; + break; + } + if (!info->outdev) + info->outdev = path->dev; + info->encap[info->num_encaps].id = path->encap.id; + info->encap[info->num_encaps].proto = path->encap.proto; + info->num_encaps++; + if (path->type == DEV_PATH_PPPOE) + memcpy(info->h_dest, path->encap.h_dest, ETH_ALEN); + break; + case DEV_PATH_BRIDGE: + if (is_zero_ether_addr(info->h_source)) + memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN); + + switch (path->bridge.vlan_mode) { + case DEV_PATH_BR_VLAN_UNTAG_HW: + info->ingress_vlans |= BIT(info->num_encaps - 1); + break; + case DEV_PATH_BR_VLAN_TAG: + if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) { + info->indev = NULL; + break; + } + info->encap[info->num_encaps].id = path->bridge.vlan_id; + info->encap[info->num_encaps].proto = path->bridge.vlan_proto; + info->num_encaps++; + break; + case DEV_PATH_BR_VLAN_UNTAG: + if (WARN_ON_ONCE(info->num_encaps-- == 0)) { + info->indev = NULL; + break; + } + break; + case DEV_PATH_BR_VLAN_KEEP: + break; + } + info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT; + break; + default: + info->indev = NULL; + break; + } + } + if (!info->outdev) + info->outdev = info->indev; + + info->hw_outdev = info->indev; + + if (nf_flowtable_hw_offload(flowtable) && + nft_is_valid_ether_device(info->indev)) + info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT; +} + +static bool nft_flowtable_find_dev(const struct net_device *dev, + struct nft_flowtable *ft) +{ + struct nft_hook *hook; + bool found = false; + + list_for_each_entry_rcu(hook, &ft->hook_list, list) { + if (!nft_hook_find_ops_rcu(hook, dev)) + continue; + + found = true; + break; + } + + return found; +} + +static void nft_dev_forward_path(struct nf_flow_route *route, + const struct nf_conn *ct, + enum ip_conntrack_dir dir, + struct nft_flowtable *ft) +{ + const struct dst_entry *dst = route->tuple[dir].dst; + struct net_device_path_stack stack; + struct nft_forward_info info = {}; + unsigned char ha[ETH_ALEN]; + int i; + + if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) >= 0) + nft_dev_path_info(&stack, &info, ha, &ft->data); + + if (!info.indev || !nft_flowtable_find_dev(info.indev, ft)) + return; + + route->tuple[!dir].in.ifindex = info.indev->ifindex; + for (i = 0; i < info.num_encaps; i++) { + route->tuple[!dir].in.encap[i].id = info.encap[i].id; + route->tuple[!dir].in.encap[i].proto = info.encap[i].proto; + } + route->tuple[!dir].in.num_encaps = info.num_encaps; + route->tuple[!dir].in.ingress_vlans = info.ingress_vlans; + + if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT) { + memcpy(route->tuple[dir].out.h_source, info.h_source, ETH_ALEN); + memcpy(route->tuple[dir].out.h_dest, info.h_dest, ETH_ALEN); + route->tuple[dir].out.ifindex = info.outdev->ifindex; + route->tuple[dir].out.hw_ifindex = info.hw_outdev->ifindex; + route->tuple[dir].xmit_type = info.xmit_type; + } +} + +int nft_flow_route(const struct nft_pktinfo *pkt, const struct nf_conn *ct, + struct nf_flow_route *route, enum ip_conntrack_dir dir, + struct nft_flowtable *ft) +{ + struct dst_entry *this_dst = skb_dst(pkt->skb); + struct dst_entry *other_dst = NULL; + struct flowi fl; + + memset(&fl, 0, sizeof(fl)); + switch (nft_pf(pkt)) { + case NFPROTO_IPV4: + fl.u.ip4.daddr = ct->tuplehash[dir].tuple.src.u3.ip; + fl.u.ip4.saddr = ct->tuplehash[!dir].tuple.src.u3.ip; + fl.u.ip4.flowi4_oif = nft_in(pkt)->ifindex; + fl.u.ip4.flowi4_iif = this_dst->dev->ifindex; + fl.u.ip4.flowi4_dscp = ip4h_dscp(ip_hdr(pkt->skb)); + fl.u.ip4.flowi4_mark = pkt->skb->mark; + fl.u.ip4.flowi4_flags = FLOWI_FLAG_ANYSRC; + break; + case NFPROTO_IPV6: + fl.u.ip6.daddr = ct->tuplehash[dir].tuple.src.u3.in6; + fl.u.ip6.saddr = ct->tuplehash[!dir].tuple.src.u3.in6; + fl.u.ip6.flowi6_oif = nft_in(pkt)->ifindex; + fl.u.ip6.flowi6_iif = this_dst->dev->ifindex; + fl.u.ip6.flowlabel = ip6_flowinfo(ipv6_hdr(pkt->skb)); + fl.u.ip6.flowi6_mark = pkt->skb->mark; + fl.u.ip6.flowi6_flags = FLOWI_FLAG_ANYSRC; + break; + } + + if (!dst_hold_safe(this_dst)) + return -ENOENT; + + nf_route(nft_net(pkt), &other_dst, &fl, false, nft_pf(pkt)); + if (!other_dst) { + dst_release(this_dst); + return -ENOENT; + } + + nft_default_forward_path(route, this_dst, dir); + nft_default_forward_path(route, other_dst, !dir); + + if (route->tuple[dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH && + route->tuple[!dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH) { + nft_dev_forward_path(route, ct, dir, ft); + nft_dev_forward_path(route, ct, !dir, ft); + } + + return 0; +} +EXPORT_SYMBOL_GPL(nft_flow_route); diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c index d3e158ecf729..cd84f2aa08f2 100644 --- a/net/netfilter/nf_nat_core.c +++ b/net/netfilter/nf_nat_core.c @@ -1187,6 +1187,16 @@ int nf_nat_register_fn(struct net *net, u8 pf, const struct nf_hook_ops *ops, struct nf_hook_ops *nat_ops; int i, ret; +#ifndef MODULE + /* If nf_nat_core is built-in and nf_nat_init() fails, dependent + * modules like nft_chain_nat.ko may still call this function. + * However, nat_net would be invalid, likely pointing to some other + * per-net structure. + */ + if (WARN_ON_ONCE(!nf_nat_hook)) + return -EOPNOTSUPP; +#endif + if (WARN_ON_ONCE(pf >= ARRAY_SIZE(nat_net->nat_proto_net))) return -EINVAL; diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c index 00838c0cc5bb..f7be30c69b5c 100644 --- a/net/netfilter/nf_nat_sip.c +++ b/net/netfilter/nf_nat_sip.c @@ -283,13 +283,24 @@ next: /* Mangle destination port for Cisco phones, then fix up checksums */ if (dir == IP_CT_DIR_REPLY && ct_sip_info->forced_dport) { + int doff = *dptr - (const char *)skb->data; struct udphdr *uh; + if (doff <= 0) { + DEBUG_NET_WARN_ON_ONCE(1); + return NF_DROP; + } + + /* ct_sip_info->forced_dport only expected with UDP */ + if (nf_ct_protonum(ct) != IPPROTO_UDP) + return NF_DROP; + if (skb_ensure_writable(skb, skb->len)) { nf_ct_helper_log(skb, ct, "cannot mangle packet"); return NF_DROP; } + *dptr = skb->data + doff; uh = (void *)skb->data + protoff; uh->dest = ct_sip_info->forced_dport; diff --git a/net/netfilter/nf_queue.c b/net/netfilter/nf_queue.c index dd416c8532c5..c0c9ea5fc4e1 100644 --- a/net/netfilter/nf_queue.c +++ b/net/netfilter/nf_queue.c @@ -67,6 +67,7 @@ static void nf_queue_entry_release_refs(struct nf_queue_entry *entry) nf_queue_sock_put(state->sk); #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) + dev_put(entry->bridge_dev); dev_put(entry->physin); dev_put(entry->physout); #endif @@ -83,6 +84,8 @@ static void __nf_queue_entry_init_physdevs(struct nf_queue_entry *entry) { #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) const struct sk_buff *skb = entry->skb; + struct dst_entry *dst = skb_dst(skb); + struct net_device *dev = NULL; if (nf_bridge_info_exists(skb)) { entry->physin = nf_bridge_get_physindev(skb, entry->state.net); @@ -91,6 +94,16 @@ static void __nf_queue_entry_init_physdevs(struct nf_queue_entry *entry) entry->physin = NULL; entry->physout = NULL; } + + if (entry->state.pf == NFPROTO_BRIDGE && + dst && (dst->flags & DST_FAKE_RTABLE)) + dev = dst_dev_rcu(dst); + + /* Must hold a reference on the bridge device: dst_hold() protects + * the dst itself, but the fake rtable is embedded in bridge-private + * storage that netdevice teardown can free independently. + */ + entry->bridge_dev = dev; #endif } @@ -107,6 +120,7 @@ bool nf_queue_entry_get_refs(struct nf_queue_entry *entry) dev_hold(state->out); #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) + dev_hold(entry->bridge_dev); dev_hold(entry->physin); dev_hold(entry->physout); #endif diff --git a/net/netfilter/nf_synproxy_core.c b/net/netfilter/nf_synproxy_core.c index a277b2bd3275..69f84c44801d 100644 --- a/net/netfilter/nf_synproxy_core.c +++ b/net/netfilter/nf_synproxy_core.c @@ -183,23 +183,23 @@ synproxy_check_timestamp_cookie(struct synproxy_options *opts) opts->options |= opts->tsecr & (1 << 5) ? NF_SYNPROXY_OPT_ECN : 0; } -static unsigned int +static bool synproxy_tstamp_adjust(struct sk_buff *skb, unsigned int protoff, struct tcphdr *th, struct nf_conn *ct, enum ip_conntrack_info ctinfo, const struct nf_conn_synproxy *synproxy) { unsigned int optoff, optend; - __be32 *ptr, old; + u32 new, old; if (synproxy->tsoff == 0) - return 1; + return true; optoff = protoff + sizeof(struct tcphdr); optend = protoff + th->doff * 4; if (skb_ensure_writable(skb, optend)) - return 0; + return false; th = (struct tcphdr *)(skb->data + protoff); @@ -208,7 +208,7 @@ synproxy_tstamp_adjust(struct sk_buff *skb, unsigned int protoff, switch (op[0]) { case TCPOPT_EOL: - return 1; + return true; case TCPOPT_NOP: optoff++; continue; @@ -216,28 +216,26 @@ synproxy_tstamp_adjust(struct sk_buff *skb, unsigned int protoff, if (optoff + 1 == optend || optoff + op[1] > optend || op[1] < 2) - return 0; + return true; if (op[0] == TCPOPT_TIMESTAMP && op[1] == TCPOLEN_TIMESTAMP) { if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY) { - ptr = (__be32 *)&op[2]; - old = *ptr; - *ptr = htonl(ntohl(*ptr) - - synproxy->tsoff); + old = get_unaligned_be32(&op[2]); + new = old - synproxy->tsoff; + put_unaligned_be32(new, &op[2]); } else { - ptr = (__be32 *)&op[6]; - old = *ptr; - *ptr = htonl(ntohl(*ptr) + - synproxy->tsoff); + old = get_unaligned_be32(&op[6]); + new = old + synproxy->tsoff; + put_unaligned_be32(new, &op[6]); } inet_proto_csum_replace4(&th->check, skb, - old, *ptr, false); - return 1; + cpu_to_be32(old), + cpu_to_be32(new), false); } optoff += op[1]; } } - return 1; + return true; } #ifdef CONFIG_PROC_FS @@ -748,7 +746,9 @@ ipv4_synproxy_hook(void *priv, struct sk_buff *skb, break; } - synproxy_tstamp_adjust(skb, thoff, th, ct, ctinfo, synproxy); + if (!synproxy_tstamp_adjust(skb, thoff, th, ct, ctinfo, synproxy)) + return NF_DROP_REASON(skb, SKB_DROP_REASON_NETFILTER_DROP, ENOMEM); + return NF_ACCEPT; } EXPORT_SYMBOL_GPL(ipv4_synproxy_hook); @@ -1176,7 +1176,9 @@ ipv6_synproxy_hook(void *priv, struct sk_buff *skb, break; } - synproxy_tstamp_adjust(skb, thoff, th, ct, ctinfo, synproxy); + if (!synproxy_tstamp_adjust(skb, thoff, th, ct, ctinfo, synproxy)) + return NF_DROP_REASON(skb, SKB_DROP_REASON_NETFILTER_DROP, ENOMEM); + return NF_ACCEPT; } EXPORT_SYMBOL_GPL(ipv6_synproxy_hook); diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 64e6a95439ab..0842916c19f5 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -6552,6 +6552,9 @@ static int nft_get_set_elem(struct nft_ctx *ctx, const struct nft_set *set, if (err < 0) return err; + if (!elem.priv) + return 0; + err = -ENOMEM; skb = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC); if (skb == NULL) diff --git a/net/netfilter/nfnetlink_cthelper.c b/net/netfilter/nfnetlink_cthelper.c index 71a248cca746..eee6dbebb2b1 100644 --- a/net/netfilter/nfnetlink_cthelper.c +++ b/net/netfilter/nfnetlink_cthelper.c @@ -41,8 +41,9 @@ static int nfnl_userspace_cthelper(struct sk_buff *skb, unsigned int protoff, struct nf_conn *ct, enum ip_conntrack_info ctinfo) { - const struct nf_conn_help *help; struct nf_conntrack_helper *helper; + const struct nf_conn_help *help; + unsigned int helper_flags; help = nfct_help(ct); if (help == NULL) @@ -53,8 +54,10 @@ nfnl_userspace_cthelper(struct sk_buff *skb, unsigned int protoff, if (helper == NULL) return NF_DROP; + helper_flags = READ_ONCE(helper->flags); + /* This is a user-space helper not yet configured, skip. */ - if ((helper->flags & + if ((helper_flags & (NF_CT_HELPER_F_USERSPACE | NF_CT_HELPER_F_CONFIGURED)) == NF_CT_HELPER_F_USERSPACE) return NF_ACCEPT; @@ -348,8 +351,8 @@ static int nfnl_cthelper_update_policy_all(struct nlattr *tb[], for (i = 0; i < helper->expect_class_max + 1; i++) { policy = (struct nf_conntrack_expect_policy *) &helper->expect_policy[i]; - policy->max_expected = new_policy->max_expected; - policy->timeout = new_policy->timeout; + policy->max_expected = new_policy[i].max_expected; + policy->timeout = new_policy[i].timeout; } err: @@ -406,10 +409,10 @@ nfnl_cthelper_update(const struct nlattr * const tb[], switch(status) { case NFCT_HELPER_STATUS_ENABLED: - helper->flags |= NF_CT_HELPER_F_CONFIGURED; + WRITE_ONCE(helper->flags, helper->flags | NF_CT_HELPER_F_CONFIGURED); break; case NFCT_HELPER_STATUS_DISABLED: - helper->flags &= ~NF_CT_HELPER_F_CONFIGURED; + WRITE_ONCE(helper->flags, helper->flags & ~NF_CT_HELPER_F_CONFIGURED); break; } } @@ -531,8 +534,8 @@ static int nfnl_cthelper_fill_info(struct sk_buff *skb, u32 portid, u32 seq, u32 type, int event, struct nf_conntrack_helper *helper) { - struct nlmsghdr *nlh; unsigned int flags = portid ? NLM_F_MULTI : 0; + struct nlmsghdr *nlh; int status; event = nfnl_msg_type(NFNL_SUBSYS_CTHELPER, event); @@ -556,7 +559,7 @@ nfnl_cthelper_fill_info(struct sk_buff *skb, u32 portid, u32 seq, u32 type, if (nla_put_be32(skb, NFCTH_PRIV_DATA_LEN, htonl(helper->data_len))) goto nla_put_failure; - if (helper->flags & NF_CT_HELPER_F_CONFIGURED) + if (READ_ONCE(helper->flags) & NF_CT_HELPER_F_CONFIGURED) status = NFCT_HELPER_STATUS_ENABLED; else status = NFCT_HELPER_STATUS_DISABLED; @@ -577,6 +580,7 @@ static int nfnl_cthelper_dump_table(struct sk_buff *skb, struct netlink_callback *cb) { struct nf_conntrack_helper *cur, *last; + unsigned int helper_flags; rcu_read_lock(); last = (struct nf_conntrack_helper *)cb->args[1]; @@ -585,8 +589,10 @@ restart: hlist_for_each_entry_rcu(cur, &nf_ct_helper_hash[cb->args[0]], hnode) { + helper_flags = READ_ONCE(cur->flags); + /* skip non-userspace conntrack helpers. */ - if (!(cur->flags & NF_CT_HELPER_F_USERSPACE)) + if (!(helper_flags & NF_CT_HELPER_F_USERSPACE)) continue; if (cb->args[1]) { diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c index c89efb951994..b9a0257fa2be 100644 --- a/net/netfilter/nfnetlink_osf.c +++ b/net/netfilter/nfnetlink_osf.c @@ -95,11 +95,7 @@ static bool nf_osf_match_one(const struct sk_buff *skb, switch (*optp) { case OSFOPT_MSS: - mss = optp[3]; - mss <<= 8; - mss |= optp[2]; - - mss = ntohs((__force __be16)mss); + mss = get_unaligned_be16(&optp[2]); break; case OSFOPT_TS: break; diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index aa870e29b500..a65d9a5cb0f0 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -1253,6 +1253,9 @@ dev_cmp(struct nf_queue_entry *entry, unsigned long ifindex) if (physinif == ifindex || physoutif == ifindex) return 1; + + if (entry->bridge_dev && entry->bridge_dev->ifindex == ifindex) + return 1; #endif if (entry->skb_dev && entry->skb_dev->ifindex == ifindex) return 1; diff --git a/net/netfilter/nft_compat.c b/net/netfilter/nft_compat.c index 08f620311b03..a77ffe2fcea7 100644 --- a/net/netfilter/nft_compat.c +++ b/net/netfilter/nft_compat.c @@ -389,6 +389,22 @@ static int nft_target_validate(const struct nft_ctx *ctx, return 0; } +static int nft_target_bridge_validate(const struct nft_ctx *ctx, + const struct nft_expr *expr) +{ + struct xt_target *target = expr->ops->data; + + /* Do not allow UNSPEC to stand-in for NFPROTO_BRIDGE + * targets: they are incompatible. ebtables targets return + * EBT_ACCEPT, DROP and so on which are not compatible with + * NF_ACCEPT, NF_DROP and so on. + */ + if (target->family != NFPROTO_BRIDGE) + return -ENOENT; + + return nft_target_validate(ctx, expr); +} + static void __nft_match_eval(const struct nft_expr *expr, struct nft_regs *regs, const struct nft_pktinfo *pkt, @@ -916,14 +932,16 @@ nft_target_select_ops(const struct nft_ctx *ctx, ops->init = nft_target_init; ops->destroy = nft_target_destroy; ops->dump = nft_target_dump; - ops->validate = nft_target_validate; ops->data = target; ops->reduce = NFT_REDUCE_READONLY; - if (family == NFPROTO_BRIDGE) + if (family == NFPROTO_BRIDGE) { ops->eval = nft_target_eval_bridge; - else + ops->validate = nft_target_bridge_validate; + } else { ops->eval = nft_target_eval_xt; + ops->validate = nft_target_validate; + } return ops; err: diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c index e95e5f59a3d6..b8f76c9057fd 100644 --- a/net/netfilter/nft_flow_offload.c +++ b/net/netfilter/nft_flow_offload.c @@ -20,265 +20,6 @@ struct nft_flow_offload { struct nft_flowtable *flowtable; }; -static enum flow_offload_xmit_type nft_xmit_type(struct dst_entry *dst) -{ - if (dst_xfrm(dst)) - return FLOW_OFFLOAD_XMIT_XFRM; - - return FLOW_OFFLOAD_XMIT_NEIGH; -} - -static void nft_default_forward_path(struct nf_flow_route *route, - struct dst_entry *dst_cache, - enum ip_conntrack_dir dir) -{ - route->tuple[!dir].in.ifindex = dst_cache->dev->ifindex; - route->tuple[dir].dst = dst_cache; - route->tuple[dir].xmit_type = nft_xmit_type(dst_cache); -} - -static bool nft_is_valid_ether_device(const struct net_device *dev) -{ - if (!dev || (dev->flags & IFF_LOOPBACK) || dev->type != ARPHRD_ETHER || - dev->addr_len != ETH_ALEN || !is_valid_ether_addr(dev->dev_addr)) - return false; - - return true; -} - -static int nft_dev_fill_forward_path(const struct nf_flow_route *route, - const struct dst_entry *dst_cache, - const struct nf_conn *ct, - enum ip_conntrack_dir dir, u8 *ha, - struct net_device_path_stack *stack) -{ - const void *daddr = &ct->tuplehash[!dir].tuple.src.u3; - struct net_device *dev = dst_cache->dev; - struct neighbour *n; - u8 nud_state; - - if (!nft_is_valid_ether_device(dev)) - goto out; - - n = dst_neigh_lookup(dst_cache, daddr); - if (!n) - return -1; - - read_lock_bh(&n->lock); - nud_state = n->nud_state; - ether_addr_copy(ha, n->ha); - read_unlock_bh(&n->lock); - neigh_release(n); - - if (!(nud_state & NUD_VALID)) - return -1; - -out: - return dev_fill_forward_path(dev, ha, stack); -} - -struct nft_forward_info { - const struct net_device *indev; - const struct net_device *outdev; - const struct net_device *hw_outdev; - struct id { - __u16 id; - __be16 proto; - } encap[NF_FLOW_TABLE_ENCAP_MAX]; - u8 num_encaps; - u8 ingress_vlans; - u8 h_source[ETH_ALEN]; - u8 h_dest[ETH_ALEN]; - enum flow_offload_xmit_type xmit_type; -}; - -static void nft_dev_path_info(const struct net_device_path_stack *stack, - struct nft_forward_info *info, - unsigned char *ha, struct nf_flowtable *flowtable) -{ - const struct net_device_path *path; - int i; - - memcpy(info->h_dest, ha, ETH_ALEN); - - for (i = 0; i < stack->num_paths; i++) { - path = &stack->path[i]; - switch (path->type) { - case DEV_PATH_ETHERNET: - case DEV_PATH_DSA: - case DEV_PATH_VLAN: - case DEV_PATH_PPPOE: - info->indev = path->dev; - if (is_zero_ether_addr(info->h_source)) - memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN); - - if (path->type == DEV_PATH_ETHERNET) - break; - if (path->type == DEV_PATH_DSA) { - i = stack->num_paths; - break; - } - - /* DEV_PATH_VLAN and DEV_PATH_PPPOE */ - if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) { - info->indev = NULL; - break; - } - if (!info->outdev) - info->outdev = path->dev; - info->encap[info->num_encaps].id = path->encap.id; - info->encap[info->num_encaps].proto = path->encap.proto; - info->num_encaps++; - if (path->type == DEV_PATH_PPPOE) - memcpy(info->h_dest, path->encap.h_dest, ETH_ALEN); - break; - case DEV_PATH_BRIDGE: - if (is_zero_ether_addr(info->h_source)) - memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN); - - switch (path->bridge.vlan_mode) { - case DEV_PATH_BR_VLAN_UNTAG_HW: - info->ingress_vlans |= BIT(info->num_encaps - 1); - break; - case DEV_PATH_BR_VLAN_TAG: - if (info->num_encaps >= NF_FLOW_TABLE_ENCAP_MAX) { - info->indev = NULL; - break; - } - info->encap[info->num_encaps].id = path->bridge.vlan_id; - info->encap[info->num_encaps].proto = path->bridge.vlan_proto; - info->num_encaps++; - break; - case DEV_PATH_BR_VLAN_UNTAG: - if (WARN_ON_ONCE(info->num_encaps-- == 0)) { - info->indev = NULL; - break; - } - break; - case DEV_PATH_BR_VLAN_KEEP: - break; - } - info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT; - break; - default: - info->indev = NULL; - break; - } - } - if (!info->outdev) - info->outdev = info->indev; - - info->hw_outdev = info->indev; - - if (nf_flowtable_hw_offload(flowtable) && - nft_is_valid_ether_device(info->indev)) - info->xmit_type = FLOW_OFFLOAD_XMIT_DIRECT; -} - -static bool nft_flowtable_find_dev(const struct net_device *dev, - struct nft_flowtable *ft) -{ - struct nft_hook *hook; - bool found = false; - - list_for_each_entry_rcu(hook, &ft->hook_list, list) { - if (!nft_hook_find_ops_rcu(hook, dev)) - continue; - - found = true; - break; - } - - return found; -} - -static void nft_dev_forward_path(struct nf_flow_route *route, - const struct nf_conn *ct, - enum ip_conntrack_dir dir, - struct nft_flowtable *ft) -{ - const struct dst_entry *dst = route->tuple[dir].dst; - struct net_device_path_stack stack; - struct nft_forward_info info = {}; - unsigned char ha[ETH_ALEN]; - int i; - - if (nft_dev_fill_forward_path(route, dst, ct, dir, ha, &stack) >= 0) - nft_dev_path_info(&stack, &info, ha, &ft->data); - - if (!info.indev || !nft_flowtable_find_dev(info.indev, ft)) - return; - - route->tuple[!dir].in.ifindex = info.indev->ifindex; - for (i = 0; i < info.num_encaps; i++) { - route->tuple[!dir].in.encap[i].id = info.encap[i].id; - route->tuple[!dir].in.encap[i].proto = info.encap[i].proto; - } - route->tuple[!dir].in.num_encaps = info.num_encaps; - route->tuple[!dir].in.ingress_vlans = info.ingress_vlans; - - if (info.xmit_type == FLOW_OFFLOAD_XMIT_DIRECT) { - memcpy(route->tuple[dir].out.h_source, info.h_source, ETH_ALEN); - memcpy(route->tuple[dir].out.h_dest, info.h_dest, ETH_ALEN); - route->tuple[dir].out.ifindex = info.outdev->ifindex; - route->tuple[dir].out.hw_ifindex = info.hw_outdev->ifindex; - route->tuple[dir].xmit_type = info.xmit_type; - } -} - -static int nft_flow_route(const struct nft_pktinfo *pkt, - const struct nf_conn *ct, - struct nf_flow_route *route, - enum ip_conntrack_dir dir, - struct nft_flowtable *ft) -{ - struct dst_entry *this_dst = skb_dst(pkt->skb); - struct dst_entry *other_dst = NULL; - struct flowi fl; - - memset(&fl, 0, sizeof(fl)); - switch (nft_pf(pkt)) { - case NFPROTO_IPV4: - fl.u.ip4.daddr = ct->tuplehash[dir].tuple.src.u3.ip; - fl.u.ip4.saddr = ct->tuplehash[!dir].tuple.src.u3.ip; - fl.u.ip4.flowi4_oif = nft_in(pkt)->ifindex; - fl.u.ip4.flowi4_iif = this_dst->dev->ifindex; - fl.u.ip4.flowi4_dscp = ip4h_dscp(ip_hdr(pkt->skb)); - fl.u.ip4.flowi4_mark = pkt->skb->mark; - fl.u.ip4.flowi4_flags = FLOWI_FLAG_ANYSRC; - break; - case NFPROTO_IPV6: - fl.u.ip6.daddr = ct->tuplehash[dir].tuple.src.u3.in6; - fl.u.ip6.saddr = ct->tuplehash[!dir].tuple.src.u3.in6; - fl.u.ip6.flowi6_oif = nft_in(pkt)->ifindex; - fl.u.ip6.flowi6_iif = this_dst->dev->ifindex; - fl.u.ip6.flowlabel = ip6_flowinfo(ipv6_hdr(pkt->skb)); - fl.u.ip6.flowi6_mark = pkt->skb->mark; - fl.u.ip6.flowi6_flags = FLOWI_FLAG_ANYSRC; - break; - } - - if (!dst_hold_safe(this_dst)) - return -ENOENT; - - nf_route(nft_net(pkt), &other_dst, &fl, false, nft_pf(pkt)); - if (!other_dst) { - dst_release(this_dst); - return -ENOENT; - } - - nft_default_forward_path(route, this_dst, dir); - nft_default_forward_path(route, other_dst, !dir); - - if (route->tuple[dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH && - route->tuple[!dir].xmit_type == FLOW_OFFLOAD_XMIT_NEIGH) { - nft_dev_forward_path(route, ct, dir, ft); - nft_dev_forward_path(route, ct, !dir, ft); - } - - return 0; -} - static bool nft_flow_offload_skip(struct sk_buff *skb, int family) { if (skb_sec_path(skb)) diff --git a/net/netfilter/nft_lookup.c b/net/netfilter/nft_lookup.c index 58c5b14889c4..beca9c7705e0 100644 --- a/net/netfilter/nft_lookup.c +++ b/net/netfilter/nft_lookup.c @@ -103,13 +103,13 @@ void nft_lookup_eval(const struct nft_expr *expr, bool found; ext = nft_set_do_lookup(net, set, ®s->data[priv->sreg]); + if (!ext) + ext = nft_set_catchall_lookup(net, set); + found = !!ext ^ priv->invert; if (!found) { - ext = nft_set_catchall_lookup(net, set); - if (!ext) { - regs->verdict.code = NFT_BREAK; - return; - } + regs->verdict.code = NFT_BREAK; + return; } if (ext) { diff --git a/net/netfilter/nft_meta.c b/net/netfilter/nft_meta.c index 05cd1e6e6a2f..d3b0efec036a 100644 --- a/net/netfilter/nft_meta.c +++ b/net/netfilter/nft_meta.c @@ -580,8 +580,8 @@ static int nft_meta_get_validate_xfrm(const struct nft_ctx *ctx) #endif } -static int nft_meta_get_validate(const struct nft_ctx *ctx, - const struct nft_expr *expr) +int nft_meta_get_validate(const struct nft_ctx *ctx, + const struct nft_expr *expr) { const struct nft_meta *priv = nft_expr_priv(expr); @@ -597,6 +597,7 @@ static int nft_meta_get_validate(const struct nft_ctx *ctx, return 0; } +EXPORT_SYMBOL_GPL(nft_meta_get_validate); int nft_meta_set_validate(const struct nft_ctx *ctx, const struct nft_expr *expr) diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c index b0214418f75a..da1c6bf0dc57 100644 --- a/net/netfilter/nft_payload.c +++ b/net/netfilter/nft_payload.c @@ -224,11 +224,17 @@ static int nft_payload_init(const struct nft_ctx *ctx, const struct nlattr * const tb[]) { struct nft_payload *priv = nft_expr_priv(expr); + u32 offset; + int err; priv->base = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_BASE])); - priv->offset = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_OFFSET])); priv->len = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_LEN])); + err = nft_parse_u32_check(tb[NFTA_PAYLOAD_OFFSET], U16_MAX, &offset); + if (err < 0) + return err; + priv->offset = offset; + return nft_parse_register_store(ctx, tb[NFTA_PAYLOAD_DREG], &priv->dreg, NULL, NFT_DATA_VALUE, priv->len); @@ -648,7 +654,8 @@ static int nft_payload_inner_init(const struct nft_ctx *ctx, const struct nlattr * const tb[]) { struct nft_payload *priv = nft_expr_priv(expr); - u32 base; + u32 base, offset; + int err; if (!tb[NFTA_PAYLOAD_BASE] || !tb[NFTA_PAYLOAD_OFFSET] || !tb[NFTA_PAYLOAD_LEN] || !tb[NFTA_PAYLOAD_DREG]) @@ -666,8 +673,11 @@ static int nft_payload_inner_init(const struct nft_ctx *ctx, } priv->base = base; - priv->offset = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_OFFSET])); priv->len = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_LEN])); + err = nft_parse_u32_check(tb[NFTA_PAYLOAD_OFFSET], U16_MAX, &offset); + if (err < 0) + return err; + priv->offset = offset; return nft_parse_register_store(ctx, tb[NFTA_PAYLOAD_DREG], &priv->dreg, NULL, NFT_DATA_VALUE, diff --git a/net/netfilter/nft_set_pipapo.c b/net/netfilter/nft_set_pipapo.c index 394b78a00a6a..b377bef60b21 100644 --- a/net/netfilter/nft_set_pipapo.c +++ b/net/netfilter/nft_set_pipapo.c @@ -342,6 +342,8 @@ #include "nft_set_pipapo_avx2.h" #include "nft_set_pipapo.h" +static void nft_pipapo_abort(const struct nft_set *set); + /** * pipapo_refill() - For each set bit, set bits from selected mapping table item * @map: Bitmap to be scanned for set bits @@ -1298,7 +1300,7 @@ static int nft_pipapo_insert(const struct net *net, const struct nft_set *set, const u8 *start_p, *end_p; int i, bsize_max, err = 0; - if (!m) + if (!m || m->state == NFT_PIPAPO_CLONE_ERR) return -ENOMEM; if (nft_set_ext_exists(ext, NFT_SET_EXT_KEY_END)) @@ -1369,8 +1371,10 @@ static int nft_pipapo_insert(const struct net *net, const struct nft_set *set, else ret = pipapo_expand(f, start, end, f->groups * f->bb); - if (ret < 0) - return ret; + if (ret < 0) { + err = ret; + goto abort; + } if (f->bsize > bsize_max) bsize_max = f->bsize; @@ -1386,7 +1390,7 @@ static int nft_pipapo_insert(const struct net *net, const struct nft_set *set, err = pipapo_realloc_scratch(m, bsize_max); if (err) - return err; + goto abort; m->bsize_max = bsize_max; } else { @@ -1398,7 +1402,26 @@ static int nft_pipapo_insert(const struct net *net, const struct nft_set *set, pipapo_map(m, rulemap, e); + m->state = NFT_PIPAPO_CLONE_MOD; return 0; +abort: + DEBUG_NET_WARN_ON_ONCE(m->state == NFT_PIPAPO_CLONE_ERR); + + /* Two rollback cases: + * 1) no previous changes. nft_pipapo_abort is not + * guaranteed to be invoked (there might be no further + * add/delete requests coming after this). + * + * 2) we had previous changes: there are transaction + * records pointing to this set. Leave the rollback to + * the transaction handling. + */ + if (m->state == NFT_PIPAPO_CLONE_NEW) + nft_pipapo_abort(set); /* releases m */ + else + m->state = NFT_PIPAPO_CLONE_ERR; + + return err; } /** @@ -1476,6 +1499,7 @@ static struct nft_pipapo_match *pipapo_clone(struct nft_pipapo_match *old) dst++; } + new->state = NFT_PIPAPO_CLONE_NEW; return new; out_mt: @@ -1899,7 +1923,7 @@ nft_pipapo_deactivate(const struct net *net, const struct nft_set *set, /* removal must occur on priv->clone, if we are low on memory * we have no choice and must fail the removal request. */ - if (!m) + if (!m || m->state == NFT_PIPAPO_CLONE_ERR) return NULL; e = pipapo_get(m, (const u8 *)elem->key.val.data, diff --git a/net/netfilter/nft_set_pipapo.h b/net/netfilter/nft_set_pipapo.h index 9aee9a9eaeb7..659908c225d6 100644 --- a/net/netfilter/nft_set_pipapo.h +++ b/net/netfilter/nft_set_pipapo.h @@ -134,9 +134,16 @@ struct nft_pipapo_scratch { unsigned long __map[]; }; +enum nft_pipapo_clone_state { + NFT_PIPAPO_CLONE_NEW, + NFT_PIPAPO_CLONE_MOD, + NFT_PIPAPO_CLONE_ERR, +}; + /** * struct nft_pipapo_match - Data used for lookup and matching * @field_count: Amount of fields in set + * @state: add/delete state; used from control plane * @bsize_max: Maximum lookup table bucket size of all fields, in longs * @scratch: Preallocated per-CPU maps for partial matching results * @rcu: Matching data is swapped on commits @@ -144,6 +151,7 @@ struct nft_pipapo_scratch { */ struct nft_pipapo_match { u8 field_count; + enum nft_pipapo_clone_state state:8; unsigned int bsize_max; struct nft_pipapo_scratch * __percpu *scratch; struct rcu_head rcu; diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index 154bf2772e27..a698420ab2b8 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -184,10 +184,14 @@ nft_rbtree_get(const struct net *net, const struct nft_set *set, if (!interval || nft_set_elem_expired(interval->from)) return ERR_PTR(-ENOENT); - if (flags & NFT_SET_ELEM_INTERVAL_END) + if (flags & NFT_SET_ELEM_INTERVAL_END) { + if (!interval->to) + return NULL; + rbe = container_of(interval->to, struct nft_rbtree_elem, ext); - else + } else { rbe = container_of(interval->from, struct nft_rbtree_elem, ext); + } return &rbe->priv; } diff --git a/net/netfilter/nft_synproxy.c b/net/netfilter/nft_synproxy.c index 4d3e5a31b412..ad3e7d27f02e 100644 --- a/net/netfilter/nft_synproxy.c +++ b/net/netfilter/nft_synproxy.c @@ -23,14 +23,13 @@ static const struct nla_policy nft_synproxy_policy[NFTA_SYNPROXY_MAX + 1] = { static void nft_synproxy_tcp_options(struct synproxy_options *opts, const struct tcphdr *tcp, struct synproxy_net *snet, - struct nf_synproxy_info *info, - const struct nft_synproxy *priv) + struct nf_synproxy_info *info) { this_cpu_inc(snet->stats->syn_received); if (tcp->ece && tcp->cwr) opts->options |= NF_SYNPROXY_OPT_ECN; - opts->options &= priv->info.options; + opts->options &= info->options; opts->mss_encode = opts->mss_option; opts->mss_option = info->mss; if (opts->options & NF_SYNPROXY_OPT_TIMESTAMP) @@ -55,7 +54,7 @@ static void nft_synproxy_eval_v4(const struct nft_synproxy *priv, if (tcp->syn) { /* Initial SYN from client */ - nft_synproxy_tcp_options(opts, tcp, snet, &info, priv); + nft_synproxy_tcp_options(opts, tcp, snet, &info); synproxy_send_client_synack(net, skb, tcp, opts); consume_skb(skb); regs->verdict.code = NF_STOLEN; @@ -86,7 +85,7 @@ static void nft_synproxy_eval_v6(const struct nft_synproxy *priv, if (tcp->syn) { /* Initial SYN from client */ - nft_synproxy_tcp_options(opts, tcp, snet, &info, priv); + nft_synproxy_tcp_options(opts, tcp, snet, &info); synproxy_send_client_synack_ipv6(net, skb, tcp, opts); consume_skb(skb); regs->verdict.code = NF_STOLEN; diff --git a/net/netfilter/xt_cluster.c b/net/netfilter/xt_cluster.c index 908fd5f2c3c8..eaf2511d63f0 100644 --- a/net/netfilter/xt_cluster.c +++ b/net/netfilter/xt_cluster.c @@ -107,7 +107,7 @@ xt_cluster_mt(const struct sk_buff *skb, struct xt_action_param *par) } ct = nf_ct_get(skb, &ctinfo); - if (ct == NULL) + if (!ct || nf_ct_is_template(ct)) return false; if (ct->master) diff --git a/net/netfilter/xt_connmark.c b/net/netfilter/xt_connmark.c index 4277084de2e7..2cf27f7d59b9 100644 --- a/net/netfilter/xt_connmark.c +++ b/net/netfilter/xt_connmark.c @@ -112,6 +112,16 @@ static int connmark_tg_check(const struct xt_tgchk_param *par) return ret; } +static int connmark_tg_check_v2(const struct xt_tgchk_param *par) +{ + const struct xt_connmark_tginfo2 *info = par->targinfo; + + if (info->shift_dir > D_SHIFT_RIGHT || info->shift_bits >= 32) + return -EINVAL; + + return connmark_tg_check(par); +} + static void connmark_tg_destroy(const struct xt_tgdtor_param *par) { nf_ct_netns_put(par->net, par->family); @@ -162,7 +172,7 @@ static struct xt_target connmark_tg_reg[] __read_mostly = { .name = "CONNMARK", .revision = 2, .family = NFPROTO_IPV4, - .checkentry = connmark_tg_check, + .checkentry = connmark_tg_check_v2, .target = connmark_tg_v2, .targetsize = sizeof(struct xt_connmark_tginfo2), .destroy = connmark_tg_destroy, @@ -183,7 +193,7 @@ static struct xt_target connmark_tg_reg[] __read_mostly = { .name = "CONNMARK", .revision = 2, .family = NFPROTO_IPV6, - .checkentry = connmark_tg_check, + .checkentry = connmark_tg_check_v2, .target = connmark_tg_v2, .targetsize = sizeof(struct xt_connmark_tginfo2), .destroy = connmark_tg_destroy, diff --git a/net/netfilter/xt_nat.c b/net/netfilter/xt_nat.c index b4f7bbc3f3ca..51c7f7ce88d9 100644 --- a/net/netfilter/xt_nat.c +++ b/net/netfilter/xt_nat.c @@ -26,6 +26,15 @@ static int xt_nat_checkentry_v0(const struct xt_tgchk_param *par) static int xt_nat_checkentry(const struct xt_tgchk_param *par) { + switch (par->family) { + case NFPROTO_IPV4: + case NFPROTO_IPV6: + case NFPROTO_INET: + break; + default: + return -EINVAL; + } + return nf_ct_netns_get(par->net, par->family); } diff --git a/net/netfilter/xt_rateest.c b/net/netfilter/xt_rateest.c index b1d736c15fcb..7c05b6342578 100644 --- a/net/netfilter/xt_rateest.c +++ b/net/netfilter/xt_rateest.c @@ -16,7 +16,7 @@ xt_rateest_mt(const struct sk_buff *skb, struct xt_action_param *par) { const struct xt_rateest_match_info *info = par->matchinfo; struct gnet_stats_rate_est64 sample = {0}; - u_int32_t bps1, bps2, pps1, pps2; + u64 bps1, bps2, pps1, pps2; bool ret = true; gen_estimator_read(&info->est1->rate_est, &sample); diff --git a/net/netfilter/xt_u32.c b/net/netfilter/xt_u32.c index 1ae7f926b0df..dabbaa742874 100644 --- a/net/netfilter/xt_u32.c +++ b/net/netfilter/xt_u32.c @@ -106,7 +106,7 @@ static int u32_mt_checkentry(const struct xt_mtchk_param *par) { const struct xt_u32 *data = par->matchinfo; const struct xt_u32_test *ct; - unsigned int i; + unsigned int i, j; if (data->ntests > ARRAY_SIZE(data->tests)) return -EINVAL; @@ -117,6 +117,16 @@ static int u32_mt_checkentry(const struct xt_mtchk_param *par) if (ct->nnums > ARRAY_SIZE(ct->location) || ct->nvalues > ARRAY_SIZE(ct->value)) return -EINVAL; + + for (j = 1; j < ct->nnums; ++j) { + switch (ct->location[j].nextop) { + case XT_U32_LEFTSH: + case XT_U32_RIGHTSH: + if (ct->location[j].number >= 32) + return -EINVAL; + break; + } + } } return 0; diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index a0811e1fba65..f63c8ac74e4a 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -1800,10 +1800,10 @@ static int ovs_ct_limit_get_zone_limit(struct net *net, } else { rcu_read_lock(); limit = ct_limit_get(info, zone); - rcu_read_unlock(); err = __ovs_ct_limit_get_zone_limit( net, info->data, zone, limit, reply); + rcu_read_unlock(); if (err) return err; } diff --git a/net/openvswitch/flow_netlink.c b/net/openvswitch/flow_netlink.c index 2dc4a6c2aece..ed85279ba024 100644 --- a/net/openvswitch/flow_netlink.c +++ b/net/openvswitch/flow_netlink.c @@ -2496,13 +2496,56 @@ static inline int add_nested_action_start(struct sw_flow_actions **sfa, return used; } -static inline void add_nested_action_end(struct sw_flow_actions *sfa, - int st_offset) +static inline int add_nested_action_end(struct sw_flow_actions *sfa, + int st_offset) { - struct nlattr *a = (struct nlattr *) ((unsigned char *)sfa->actions + - st_offset); + struct nlattr *a; + u32 attr_len; + + if (WARN_ON_ONCE(st_offset < 0 || + (u32)st_offset > sfa->actions_len)) + return -EINVAL; + + attr_len = sfa->actions_len - (u32)st_offset; + if (WARN_ON_ONCE(attr_len < NLA_HDRLEN)) + return -EINVAL; - a->nla_len = sfa->actions_len - st_offset; + if (attr_len > U16_MAX) + return -EMSGSIZE; + + a = (struct nlattr *)((u8 *)sfa->actions + st_offset); + a->nla_len = attr_len; + return 0; +} + +/* Free the generated action-list tail at @start and truncate it. + * If @nested, @start points to its containing nlattr header. + */ +static void ovs_nla_trim(struct sw_flow_actions *sfa, int start, bool nested) +{ + const struct nlattr *actions; + u32 len; + + if (start < 0) + return; + + if (WARN_ON_ONCE((u32)start > sfa->actions_len)) + return; + + actions = (const struct nlattr *)((u8 *)sfa->actions + start); + len = sfa->actions_len - (u32)start; + + if (nested) { + if (len < NLA_HDRLEN) + goto out; + + actions = (const struct nlattr *)((u8 *)actions + NLA_HDRLEN); + len -= NLA_HDRLEN; + } + + ovs_nla_free_nested_actions(actions, len); +out: + sfa->actions_len = start; } static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr, @@ -2522,6 +2565,7 @@ static int validate_and_copy_sample(struct net *net, const struct nlattr *attr, const struct nlattr *attrs[OVS_SAMPLE_ATTR_MAX + 1]; const struct nlattr *probability, *actions; const struct nlattr *a; + int actions_start; int rem, start, err; struct sample_arg arg; @@ -2565,18 +2609,27 @@ static int validate_and_copy_sample(struct net *net, const struct nlattr *attr, err = ovs_nla_add_action(sfa, OVS_SAMPLE_ATTR_ARG, &arg, sizeof(arg), log); if (err) - return err; + goto err; + actions_start = (*sfa)->actions_len; err = __ovs_nla_copy_actions(net, actions, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (err) + goto err_free; return 0; + +err_free: + ovs_nla_trim(*sfa, actions_start, false); +err: + (*sfa)->actions_len = start; + return err; } static int validate_and_copy_dec_ttl(struct net *net, @@ -2624,18 +2677,31 @@ static int validate_and_copy_dec_ttl(struct net *net, return start; action_start = add_nested_action_start(sfa, OVS_DEC_TTL_ATTR_ACTION, log); - if (action_start < 0) - return action_start; + if (action_start < 0) { + err = action_start; + goto err; + } err = __ovs_nla_copy_actions(net, actions, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; + + err = add_nested_action_end(*sfa, action_start); + if (err) + goto err_free; - add_nested_action_end(*sfa, action_start); - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (err) + goto err_free; return 0; + +err_free: + ovs_nla_trim(*sfa, action_start, true); +err: + (*sfa)->actions_len = start; + return err; } static int validate_and_copy_clone(struct net *net, @@ -2646,6 +2712,7 @@ static int validate_and_copy_clone(struct net *net, u32 mpls_label_count, bool log, bool last, u32 depth) { + int actions_start; int start, err; u32 exec; @@ -2661,17 +2728,26 @@ static int validate_and_copy_clone(struct net *net, err = ovs_nla_add_action(sfa, OVS_CLONE_ATTR_EXEC, &exec, sizeof(exec), log); if (err) - return err; + goto err; + actions_start = (*sfa)->actions_len; err = __ovs_nla_copy_actions(net, attr, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (err) + goto err_free; return 0; + +err_free: + ovs_nla_trim(*sfa, actions_start, false); +err: + (*sfa)->actions_len = start; + return err; } void ovs_match_init(struct sw_flow_match *match, @@ -2763,20 +2839,20 @@ static int validate_and_copy_set_tun(const struct nlattr *attr, tun_dst = metadata_dst_alloc(key.tun_opts_len, METADATA_IP_TUNNEL, GFP_KERNEL); - if (!tun_dst) - return -ENOMEM; + if (!tun_dst) { + err = -ENOMEM; + goto err; + } err = dst_cache_init(&tun_dst->u.tun_info.dst_cache, GFP_KERNEL); - if (err) { - dst_release((struct dst_entry *)tun_dst); - return err; - } + if (err) + goto err_free_tun_dst; a = __add_action(sfa, OVS_KEY_ATTR_TUNNEL_INFO, NULL, sizeof(*ovs_tun), log); if (IS_ERR(a)) { - dst_release((struct dst_entry *)tun_dst); - return PTR_ERR(a); + err = PTR_ERR(a); + goto err_free_tun_dst; } ovs_tun = nla_data(a); @@ -2797,8 +2873,16 @@ static int validate_and_copy_set_tun(const struct nlattr *attr, ip_tunnel_info_opts_set(tun_info, TUN_METADATA_OPTS(&key, key.tun_opts_len), key.tun_opts_len, dst_opt_type); - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (WARN_ON_ONCE(err)) + goto err_free_tun_dst; + + return 0; +err_free_tun_dst: + dst_release((struct dst_entry *)tun_dst); +err: + (*sfa)->actions_len = start; return err; } @@ -2971,7 +3055,7 @@ static int validate_set(const struct nlattr *a, /* Convert non-masked non-tunnel set actions to masked set actions. */ if (!masked && key_type != OVS_KEY_ATTR_TUNNEL) { - int start, len = key_len * 2; + int err, start, len = key_len * 2; struct nlattr *at; *skip_copy = true; @@ -2983,8 +3067,11 @@ static int validate_set(const struct nlattr *a, return start; at = __add_action(sfa, key_type, NULL, len, log); - if (IS_ERR(at)) - return PTR_ERR(at); + if (IS_ERR(at)) { + err = PTR_ERR(at); + (*sfa)->actions_len = start; + return err; + } memcpy(nla_data(at), nla_data(ovs_key), key_len); /* Key. */ memset(nla_data(at) + key_len, 0xff, key_len); /* Mask. */ @@ -2994,7 +3081,11 @@ static int validate_set(const struct nlattr *a, mask->ipv6_label &= htonl(0x000FFFFF); } - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (WARN_ON_ONCE(err)) { + (*sfa)->actions_len = start; + return err; + } } return 0; @@ -3040,7 +3131,8 @@ static int validate_and_copy_check_pkt_len(struct net *net, const struct nlattr *acts_if_greater, *acts_if_lesser_eq; struct nlattr *a[OVS_CHECK_PKT_LEN_ATTR_MAX + 1]; struct check_pkt_len_arg arg; - int nested_acts_start; + int greater_acts_start = -1; + int lesser_acts_start = -1; int start, err; err = nla_parse_deprecated_strict(a, OVS_CHECK_PKT_LEN_ATTR_MAX, @@ -3075,37 +3167,58 @@ static int validate_and_copy_check_pkt_len(struct net *net, err = ovs_nla_add_action(sfa, OVS_CHECK_PKT_LEN_ATTR_ARG, &arg, sizeof(arg), log); if (err) - return err; + goto err_free; - nested_acts_start = add_nested_action_start(sfa, - OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_LESS_EQUAL, log); - if (nested_acts_start < 0) - return nested_acts_start; + lesser_acts_start = + add_nested_action_start(sfa, + OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_LESS_EQUAL, + log); + if (lesser_acts_start < 0) { + err = lesser_acts_start; + goto err_free; + } err = __ovs_nla_copy_actions(net, acts_if_lesser_eq, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; - add_nested_action_end(*sfa, nested_acts_start); + err = add_nested_action_end(*sfa, lesser_acts_start); + if (err) + goto err_free; - nested_acts_start = add_nested_action_start(sfa, - OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_GREATER, log); - if (nested_acts_start < 0) - return nested_acts_start; + greater_acts_start = + add_nested_action_start(sfa, + OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_GREATER, + log); + if (greater_acts_start < 0) { + err = greater_acts_start; + goto err_free; + } err = __ovs_nla_copy_actions(net, acts_if_greater, key, sfa, eth_type, vlan_tci, mpls_label_count, log, depth + 1); if (err) - return err; + goto err_free; + + err = add_nested_action_end(*sfa, greater_acts_start); + if (err) + goto err_free; - add_nested_action_end(*sfa, nested_acts_start); - add_nested_action_end(*sfa, start); + err = add_nested_action_end(*sfa, start); + if (err) + goto err_free; return 0; + +err_free: + ovs_nla_trim(*sfa, greater_acts_start, true); + ovs_nla_trim(*sfa, lesser_acts_start, true); + ovs_nla_trim(*sfa, start, false); + return err; } static int validate_psample(const struct nlattr *attr) diff --git a/net/phonet/datagram.c b/net/phonet/datagram.c index 976fe250b509..22cf23f06832 100644 --- a/net/phonet/datagram.c +++ b/net/phonet/datagram.c @@ -109,7 +109,7 @@ static int pn_sendmsg(struct sock *sk, struct msghdr *msg, size_t len) } static int pn_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { struct sk_buff *skb = NULL; struct sockaddr_pn sa; @@ -143,7 +143,7 @@ static int pn_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, if (msg->msg_name != NULL) { __sockaddr_check_size(sizeof(sa)); memcpy(msg->msg_name, &sa, sizeof(sa)); - *addr_len = sizeof(sa); + msg->msg_namelen = sizeof(sa); } out: diff --git a/net/phonet/pep.c b/net/phonet/pep.c index 058a16c423ae..7c695f3afc28 100644 --- a/net/phonet/pep.c +++ b/net/phonet/pep.c @@ -1276,7 +1276,7 @@ struct sk_buff *pep_read(struct sock *sk) } static int pep_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { struct sk_buff *skb; int err; diff --git a/net/psample/psample.c b/net/psample/psample.c index 25f92ba0840c..2d3bda59fa0d 100644 --- a/net/psample/psample.c +++ b/net/psample/psample.c @@ -476,15 +476,17 @@ void psample_sample_packet(struct psample_group *group, goto error; if (data_len) { - int nla_len = nla_total_size(data_len); + int nla_len = nla_attr_size(data_len); struct nlattr *nla; nla = skb_put(nl_skb, nla_len); nla->nla_type = PSAMPLE_ATTR_DATA; - nla->nla_len = nla_attr_size(data_len); + nla->nla_len = nla_len; if (skb_copy_bits(skb, 0, nla_data(nla), data_len)) goto error; + + skb_put_zero(nl_skb, nla_padlen(data_len)); } #ifdef CONFIG_INET diff --git a/net/qrtr/af_qrtr.c b/net/qrtr/af_qrtr.c index 2c009793f193..305523cebe3b 100644 --- a/net/qrtr/af_qrtr.c +++ b/net/qrtr/af_qrtr.c @@ -496,7 +496,7 @@ int qrtr_endpoint_post(struct qrtr_endpoint *ep, const void *data, size_t len) if (cb->dst_port == QRTR_PORT_CTRL_LEGACY) cb->dst_port = QRTR_PORT_CTRL; - if (!size || len != ALIGN(size, 4) + hdrlen) + if (!size || size > len || len != ALIGN(size, 4) + hdrlen) goto err; if ((cb->type == QRTR_TYPE_NEW_SERVER || diff --git a/net/rxrpc/ar-internal.h b/net/rxrpc/ar-internal.h index 98f2165159d7..d2b31d15851b 100644 --- a/net/rxrpc/ar-internal.h +++ b/net/rxrpc/ar-internal.h @@ -650,7 +650,9 @@ enum rxrpc_call_event { enum rxrpc_call_state { RXRPC_CALL_UNINITIALISED, RXRPC_CALL_CLIENT_AWAIT_CONN, /* - client waiting for connection to become available */ + RXRPC_CALL_CLIENT_PRE_SEND, /* - client is connected, but hasn't sent anything yet */ RXRPC_CALL_CLIENT_SEND_REQUEST, /* - client sending request phase */ + RXRPC_CALL_CLIENT_AWAIT_ACK, /* - client awaiting ACKs of request */ RXRPC_CALL_CLIENT_AWAIT_REPLY, /* - client awaiting reply */ RXRPC_CALL_CLIENT_RECV_REPLY, /* - client receiving reply phase */ RXRPC_CALL_SERVER_PREALLOC, /* - service preallocation */ @@ -1355,9 +1357,9 @@ static inline struct rxrpc_net *rxrpc_net(struct net *net) } /* - * out_of_band.c + * oob.c */ -void rxrpc_notify_socket_oob(struct rxrpc_call *call, struct sk_buff *skb); +bool rxrpc_notify_socket_oob(struct rxrpc_call *call, struct sk_buff *skb); void rxrpc_add_pending_oob(struct rxrpc_sock *rx, struct sk_buff *skb); int rxrpc_sendmsg_oob(struct rxrpc_sock *rx, struct msghdr *msg, size_t len); diff --git a/net/rxrpc/call_accept.c b/net/rxrpc/call_accept.c index 00982a030744..eccd13f425ea 100644 --- a/net/rxrpc/call_accept.c +++ b/net/rxrpc/call_accept.c @@ -471,13 +471,26 @@ int rxrpc_kernel_charge_accept(struct socket *sock, rxrpc_notify_rx_t notify_rx, unsigned long user_call_ID, gfp_t gfp, unsigned int debug_id) { - struct rxrpc_sock *rx = rxrpc_sk(sock->sk); - struct rxrpc_backlog *b = rx->backlog; + struct rxrpc_backlog *b; + struct rxrpc_sock *rx; + struct sock *sk; + int ret; - if (sock->sk->sk_state == RXRPC_CLOSE) - return -ESHUTDOWN; + sk = sock->sk; + rx = rxrpc_sk(sk); + + lock_sock(sk); + if (sk->sk_state != RXRPC_SERVER_LISTENING || !rx->backlog) { + ret = -ESHUTDOWN; + goto out; + } + + b = rx->backlog; + ret = rxrpc_service_prealloc_one(rx, b, notify_rx, user_call_ID, + gfp, debug_id); - return rxrpc_service_prealloc_one(rx, b, notify_rx, user_call_ID, - gfp, debug_id); +out: + release_sock(sk); + return ret; } EXPORT_SYMBOL(rxrpc_kernel_charge_accept); diff --git a/net/rxrpc/call_event.c b/net/rxrpc/call_event.c index fec59d9338b9..21be9c86d7a7 100644 --- a/net/rxrpc/call_event.c +++ b/net/rxrpc/call_event.c @@ -178,7 +178,7 @@ static void rxrpc_close_tx_phase(struct rxrpc_call *call) switch (__rxrpc_call_state(call)) { case RXRPC_CALL_CLIENT_SEND_REQUEST: - rxrpc_set_call_state(call, RXRPC_CALL_CLIENT_AWAIT_REPLY); + rxrpc_set_call_state(call, RXRPC_CALL_CLIENT_AWAIT_ACK); break; case RXRPC_CALL_SERVER_SEND_REPLY: rxrpc_set_call_state(call, RXRPC_CALL_SERVER_AWAIT_ACK); @@ -244,6 +244,8 @@ static void rxrpc_transmit_fresh_data(struct rxrpc_call *call, unsigned int limi break; } while (req.n < limit && before(seq, send_top)); + if (__rxrpc_call_state(call) == RXRPC_CALL_CLIENT_PRE_SEND) + rxrpc_set_call_state(call, RXRPC_CALL_CLIENT_SEND_REQUEST); if (txb->flags & RXRPC_LAST_PACKET) { rxrpc_close_tx_phase(call); tq = NULL; @@ -267,6 +269,7 @@ void rxrpc_transmit_some_data(struct rxrpc_call *call, unsigned int limit, fallthrough; case RXRPC_CALL_SERVER_SEND_REPLY: + case RXRPC_CALL_CLIENT_PRE_SEND: case RXRPC_CALL_CLIENT_SEND_REQUEST: if (!rxrpc_tx_window_space(call)) return; diff --git a/net/rxrpc/call_object.c b/net/rxrpc/call_object.c index fcb9d38bb521..817ed9acb91e 100644 --- a/net/rxrpc/call_object.c +++ b/net/rxrpc/call_object.c @@ -18,7 +18,9 @@ const char *const rxrpc_call_states[NR__RXRPC_CALL_STATES] = { [RXRPC_CALL_UNINITIALISED] = "Uninit ", [RXRPC_CALL_CLIENT_AWAIT_CONN] = "ClWtConn", + [RXRPC_CALL_CLIENT_PRE_SEND] = "ClPreSnd", [RXRPC_CALL_CLIENT_SEND_REQUEST] = "ClSndReq", + [RXRPC_CALL_CLIENT_AWAIT_ACK] = "ClAwtAck", [RXRPC_CALL_CLIENT_AWAIT_REPLY] = "ClAwtRpl", [RXRPC_CALL_CLIENT_RECV_REPLY] = "ClRcvRpl", [RXRPC_CALL_SERVER_PREALLOC] = "SvPrealc", diff --git a/net/rxrpc/conn_client.c b/net/rxrpc/conn_client.c index 63bbcc567f59..ef8df142c16d 100644 --- a/net/rxrpc/conn_client.c +++ b/net/rxrpc/conn_client.c @@ -449,7 +449,7 @@ static void rxrpc_activate_one_channel(struct rxrpc_connection *conn, trace_rxrpc_connect_call(call); call->tx_last_sent = ktime_get_real(); rxrpc_start_call_timer(call); - rxrpc_set_call_state(call, RXRPC_CALL_CLIENT_SEND_REQUEST); + rxrpc_set_call_state(call, RXRPC_CALL_CLIENT_PRE_SEND); wake_up(&call->waitq); } diff --git a/net/rxrpc/conn_event.c b/net/rxrpc/conn_event.c index c96ca615b787..611c790bc6d0 100644 --- a/net/rxrpc/conn_event.c +++ b/net/rxrpc/conn_event.c @@ -436,7 +436,7 @@ static bool rxrpc_post_challenge(struct rxrpc_connection *conn, struct rxrpc_skb_priv *sp = rxrpc_skb(skb); struct rxrpc_call *call = NULL; struct rxrpc_sock *rx; - bool respond = false; + bool respond = false, queued = false; sp->chall.conn = rxrpc_get_connection(conn, rxrpc_conn_get_challenge_input); @@ -472,8 +472,13 @@ static bool rxrpc_post_challenge(struct rxrpc_connection *conn, } if (call) - rxrpc_notify_socket_oob(call, skb); + queued = rxrpc_notify_socket_oob(call, skb); rcu_read_unlock(); + if (call && !queued) { + rxrpc_put_connection(conn, rxrpc_conn_put_challenge_input); + sp->chall.conn = NULL; + return false; + } if (!call) rxrpc_post_packet_to_conn(conn, skb); diff --git a/net/rxrpc/input.c b/net/rxrpc/input.c index ce761466b02d..73cafe6bfa9f 100644 --- a/net/rxrpc/input.c +++ b/net/rxrpc/input.c @@ -181,7 +181,8 @@ void rxrpc_congestion_degrade(struct rxrpc_call *call) if (call->cong_ca_state != RXRPC_CA_SLOW_START && call->cong_ca_state != RXRPC_CA_CONGEST_AVOIDANCE) return; - if (__rxrpc_call_state(call) == RXRPC_CALL_CLIENT_AWAIT_REPLY) + if (__rxrpc_call_state(call) == RXRPC_CALL_CLIENT_AWAIT_ACK || + __rxrpc_call_state(call) == RXRPC_CALL_CLIENT_AWAIT_REPLY) return; rtt = ns_to_ktime(call->srtt_us * (NSEC_PER_USEC / 8)); @@ -236,6 +237,9 @@ static bool rxrpc_rotate_tx_window(struct rxrpc_call *call, rxrpc_seq_t to, call->acks_lowest_nak = to; } + if (after(seq, to)) + return false; + /* We may have a left over fully-consumed buffer at the front that we * couldn't drop before (rotate_and_keep below). */ @@ -247,7 +251,7 @@ static bool rxrpc_rotate_tx_window(struct rxrpc_call *call, rxrpc_seq_t to, tq = call->tx_queue; } - do { + while (before_eq(seq, to)) { unsigned int ix = seq - call->tx_qbase; _debug("tq=%x seq=%x i=%d f=%x", tq->qbase, seq, ix, tq->bufs[ix]->flags); @@ -317,8 +321,7 @@ static bool rxrpc_rotate_tx_window(struct rxrpc_call *call, rxrpc_seq_t to, break; } } - - } while (before_eq(seq, to)); + } if (trace) trace_rxrpc_rack_update(call, summary); @@ -356,6 +359,7 @@ static void rxrpc_end_tx_phase(struct rxrpc_call *call, bool reply_begun, switch (__rxrpc_call_state(call)) { case RXRPC_CALL_CLIENT_SEND_REQUEST: + case RXRPC_CALL_CLIENT_AWAIT_ACK: case RXRPC_CALL_CLIENT_AWAIT_REPLY: if (reply_begun) { rxrpc_set_call_state(call, RXRPC_CALL_CLIENT_RECV_REPLY); @@ -392,6 +396,14 @@ static bool rxrpc_receiving_reply(struct rxrpc_call *call) trace_rxrpc_timer_can(call, rxrpc_timer_trace_delayed_ack); } + /* Deal with an apparent reply coming in before we've got the request + * queued or transmitted. + */ + if (!test_bit(RXRPC_CALL_EXPOSED, &call->flags)) { + rxrpc_proto_abort(call, top, rxrpc_eproto_early_reply); + return false; + } + if (!test_bit(RXRPC_CALL_TX_LAST, &call->flags)) { if (!rxrpc_rotate_tx_window(call, top, &summary)) { rxrpc_proto_abort(call, top, rxrpc_eproto_early_reply); @@ -694,6 +706,7 @@ static void rxrpc_input_data(struct rxrpc_call *call, struct sk_buff *skb) switch (__rxrpc_call_state(call)) { case RXRPC_CALL_CLIENT_SEND_REQUEST: + case RXRPC_CALL_CLIENT_AWAIT_ACK: case RXRPC_CALL_CLIENT_AWAIT_REPLY: /* Received data implicitly ACKs all of the request * packets we sent when we're acting as a client. @@ -1154,10 +1167,12 @@ static void rxrpc_input_ack(struct rxrpc_call *call, struct sk_buff *skb) if (hard_ack + 1 == 0) return rxrpc_proto_abort(call, 0, rxrpc_eproto_ackr_zero); - /* Ignore ACKs unless we are or have just been transmitting. */ + /* Ignore ACKs unless we are transmitting or are waiting for + * acknowledgement of the packets we've just been transmitting. + */ switch (__rxrpc_call_state(call)) { case RXRPC_CALL_CLIENT_SEND_REQUEST: - case RXRPC_CALL_CLIENT_AWAIT_REPLY: + case RXRPC_CALL_CLIENT_AWAIT_ACK: case RXRPC_CALL_SERVER_SEND_REPLY: case RXRPC_CALL_SERVER_AWAIT_ACK: break; @@ -1215,7 +1230,17 @@ static void rxrpc_input_ackall(struct rxrpc_call *call, struct sk_buff *skb) { struct rxrpc_ack_summary summary = { 0 }; - if (rxrpc_rotate_tx_window(call, call->tx_top, &summary)) + switch (__rxrpc_call_state(call)) { + case RXRPC_CALL_CLIENT_SEND_REQUEST: + case RXRPC_CALL_CLIENT_AWAIT_ACK: + case RXRPC_CALL_SERVER_SEND_REPLY: + case RXRPC_CALL_SERVER_AWAIT_ACK: + break; + default: + return; + } + + if (rxrpc_rotate_tx_window(call, call->tx_transmitted, &summary)) rxrpc_end_tx_phase(call, false, rxrpc_eproto_unexpected_ackall); } diff --git a/net/rxrpc/oob.c b/net/rxrpc/oob.c index 05ca9c1faa57..c80ee2487d09 100644 --- a/net/rxrpc/oob.c +++ b/net/rxrpc/oob.c @@ -32,11 +32,12 @@ struct rxrpc_oob_params { * Post an out-of-band message for attention by the socket or kernel service * associated with a reference call. */ -void rxrpc_notify_socket_oob(struct rxrpc_call *call, struct sk_buff *skb) +bool rxrpc_notify_socket_oob(struct rxrpc_call *call, struct sk_buff *skb) { struct rxrpc_skb_priv *sp = rxrpc_skb(skb); struct rxrpc_sock *rx; struct sock *sk; + bool queued = false; rcu_read_lock(); @@ -49,6 +50,7 @@ void rxrpc_notify_socket_oob(struct rxrpc_call *call, struct sk_buff *skb) skb->skb_mstamp_ns = rx->oob_id_counter++; rxrpc_get_skb(skb, rxrpc_skb_get_post_oob); skb_queue_tail(&rx->recvmsg_oobq, skb); + queued = true; trace_rxrpc_notify_socket(call->debug_id, sp->hdr.serial); if (rx->app_ops) @@ -56,11 +58,12 @@ void rxrpc_notify_socket_oob(struct rxrpc_call *call, struct sk_buff *skb) } spin_unlock_irq(&rx->recvmsg_lock); - if (!rx->app_ops && !sock_flag(sk, SOCK_DEAD)) + if (queued && !rx->app_ops && !sock_flag(sk, SOCK_DEAD)) sk->sk_data_ready(sk); } rcu_read_unlock(); + return queued; } /* @@ -210,6 +213,11 @@ static int rxrpc_respond_to_oob(struct rxrpc_sock *rx, break; } + switch (skb->mark) { + case RXRPC_OOB_CHALLENGE: + rxrpc_put_connection(sp->chall.conn, rxrpc_conn_put_oob); + break; + } rxrpc_free_skb(skb, rxrpc_skb_put_oob); return ret; } diff --git a/net/rxrpc/recvmsg.c b/net/rxrpc/recvmsg.c index c940600117a4..efcba4b2e74f 100644 --- a/net/rxrpc/recvmsg.c +++ b/net/rxrpc/recvmsg.c @@ -27,8 +27,6 @@ void rxrpc_notify_socket(struct rxrpc_call *call) _enter("%d", call->debug_id); - if (!list_empty(&call->recvmsg_link)) - return; if (test_bit(RXRPC_CALL_RELEASED, &call->flags)) { rxrpc_see_call(call, rxrpc_call_see_notify_released); return; @@ -161,7 +159,7 @@ static int rxrpc_verify_data(struct rxrpc_call *call, struct sk_buff *skb) struct rxrpc_skb_priv *sp = rxrpc_skb(skb); int ret; - if (sp->len > call->rx_dec_bsize) { + if (sp->len > call->rx_dec_bsize || !call->rx_dec_buffer) { /* Make sure we can hold a 1412-byte jumbo subpacket and make * sure that the buffer size is aligned to a crypto blocksize. */ @@ -262,12 +260,13 @@ static int rxrpc_recvmsg_oob(struct socket *sock, struct msghdr *msg, break; } - if (!(flags & MSG_PEEK)) + if (!(flags & MSG_PEEK)) { skb_unlink(skb, &rx->recvmsg_oobq); - if (need_response) - rxrpc_add_pending_oob(rx, skb); - else - rxrpc_free_skb(skb, rxrpc_skb_put_oob); + if (need_response) + rxrpc_add_pending_oob(rx, skb); + else + rxrpc_free_skb(skb, rxrpc_skb_put_oob); + } return ret; } @@ -437,7 +436,8 @@ try_again: return -EAGAIN; } - if (list_empty(&rx->recvmsg_q)) { + if (list_empty(&rx->recvmsg_q) && + skb_queue_empty_lockless(&rx->recvmsg_oobq)) { ret = -EWOULDBLOCK; if (timeo == 0) { call = NULL; @@ -470,7 +470,7 @@ try_again: release_sock(&rx->sk); if (ret == -EAGAIN) goto try_again; - goto error_no_call; + goto error_trace; } /* Find the next call and dequeue it if we're not just peeking. If we @@ -529,8 +529,7 @@ try_again: if (test_bit(RXRPC_CALL_RELEASED, &call->flags)) { rxrpc_see_call(call, rxrpc_call_see_already_released); mutex_unlock(&call->user_mutex); - if (!(flags & MSG_PEEK)) - rxrpc_put_call(call, rxrpc_call_put_recvmsg); + rxrpc_put_call(call, rxrpc_call_put_recvmsg); goto try_again; } diff --git a/net/rxrpc/rxgk.c b/net/rxrpc/rxgk.c index 1ad5f7ea3280..3c2bfded7fcc 100644 --- a/net/rxrpc/rxgk.c +++ b/net/rxrpc/rxgk.c @@ -687,16 +687,17 @@ static int rxgk_issue_challenge(struct rxrpc_connection *conn) ret = do_udp_sendmsg(conn->local->socket, &msg, len); if (ret > 0) rxrpc_peer_mark_tx(conn->peer); - __free_page(page); if (ret < 0) { trace_rxrpc_tx_fail(conn->debug_id, serial, ret, rxrpc_tx_point_rxgk_challenge); + __free_page(page); return -EAGAIN; } trace_rxrpc_tx_packet(conn->debug_id, whdr, rxrpc_tx_point_rxgk_challenge); + __free_page(page); _leave(" = 0"); return 0; } diff --git a/net/rxrpc/sendmsg.c b/net/rxrpc/sendmsg.c index 39a6b21d6b8f..65e2f5626947 100644 --- a/net/rxrpc/sendmsg.c +++ b/net/rxrpc/sendmsg.c @@ -366,7 +366,8 @@ reload: if (state >= RXRPC_CALL_COMPLETE) goto maybe_error; ret = -EPROTO; - if (state != RXRPC_CALL_CLIENT_SEND_REQUEST && + if (state != RXRPC_CALL_CLIENT_PRE_SEND && + state != RXRPC_CALL_CLIENT_SEND_REQUEST && state != RXRPC_CALL_SERVER_ACK_REQUEST && state != RXRPC_CALL_SERVER_SEND_REPLY) { /* Request phase complete for this client call */ diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 6afeeb6b590a..f948646985dd 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -148,10 +148,15 @@ static void offload_action_hw_count_dec(struct tc_action *act, static unsigned int tcf_offload_act_num_actions_single(struct tc_action *act) { - if (is_tcf_pedit(act)) - return tcf_pedit_nkeys(act); - else - return 1; + unsigned int count; + + if (is_tcf_pedit(act)) { + spin_lock_bh(&act->tcfa_lock); + count = tcf_pedit_nkeys_locked(act); + spin_unlock_bh(&act->tcfa_lock); + return count; + } + return 1; } static bool tc_act_skip_hw(u32 flags) diff --git a/net/sched/act_bpf.c b/net/sched/act_bpf.c index c2b5bc19e091..284800b7d3c5 100644 --- a/net/sched/act_bpf.c +++ b/net/sched/act_bpf.c @@ -44,7 +44,7 @@ TC_INDIRECT_SCOPE int tcf_bpf_act(struct sk_buff *skb, tcf_lastuse_update(&prog->tcf_tm); bstats_update(this_cpu_ptr(prog->common.cpu_bstats), skb); - filter = rcu_dereference(prog->filter); + filter = rcu_dereference_bh(prog->filter); if (at_ingress) { __skb_push(skb, skb->mac_len); filter_res = bpf_prog_run_data_pointers(filter, skb); diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index f9cb8f7474ed..27d9a7181a0d 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -842,11 +842,11 @@ static int tcf_ct_handle_fragments(struct net *net, struct sk_buff *skb, u8 family, u16 zone, bool *defrag) { enum ip_conntrack_info ctinfo; + struct tc_skb_cb cb; struct nf_conn *ct; int err = 0; bool frag; u8 proto; - u16 mru; /* Previously seen (loopback)? Ignore. */ ct = nf_ct_get(skb, &ctinfo); @@ -860,12 +860,13 @@ static int tcf_ct_handle_fragments(struct net *net, struct sk_buff *skb, if (err || !frag) return err; - err = nf_ct_handle_fragments(net, skb, zone, family, &proto, &mru); + cb = *tc_skb_cb(skb); + err = nf_ct_handle_fragments(net, skb, zone, family, &proto, &cb.mru); if (err) return err; *defrag = true; - tc_skb_cb(skb)->mru = mru; + *tc_skb_cb(skb) = cb; return 0; } @@ -1293,7 +1294,8 @@ static int tcf_ct_fill_params(struct net *net, if (tb[TCA_CT_ZONE]) { if (!IS_ENABLED(CONFIG_NF_CONNTRACK_ZONES)) { NL_SET_ERR_MSG_MOD(extack, "Conntrack zones isn't enabled."); - return -EOPNOTSUPP; + err = -EOPNOTSUPP; + goto err; } tcf_ct_set_key_val(tb, @@ -1306,7 +1308,8 @@ static int tcf_ct_fill_params(struct net *net, tmpl = nf_ct_tmpl_alloc(net, &zone, GFP_KERNEL); if (!tmpl) { NL_SET_ERR_MSG_MOD(extack, "Failed to allocate conntrack template"); - return -ENOMEM; + err = -ENOMEM; + goto err; } p->tmpl = tmpl; if (tb[TCA_CT_HELPER_NAME]) { diff --git a/net/sched/act_pedit.c b/net/sched/act_pedit.c index c0a5f5d78dac..5aa91fb145a2 100644 --- a/net/sched/act_pedit.c +++ b/net/sched/act_pedit.c @@ -563,9 +563,18 @@ static int tcf_pedit_offload_act_setup(struct tc_action *act, void *entry_data, { if (bind) { struct flow_action_entry *entry = entry_data; + int nkeys = tcf_pedit_nkeys_locked(act); int k; - for (k = 0; k < tcf_pedit_nkeys(act); k++) { + /* If the required keys exceed the remaining capacity return + * -ENOSPC to abort the offload and fallback to software. + */ + if (nkeys > *index_inc) { + NL_SET_ERR_MSG_MOD(extack, "Not enough space to offload all pedit keys"); + return -ENOSPC; + } + + for (k = 0; k < nkeys; k++) { switch (tcf_pedit_cmd(act, k)) { case TCA_PEDIT_KEY_EX_CMD_SET: entry->id = FLOW_ACTION_MANGLE; @@ -602,7 +611,7 @@ static int tcf_pedit_offload_act_setup(struct tc_action *act, void *entry_data, return -EOPNOTSUPP; } - for (k = 1; k < tcf_pedit_nkeys(act); k++) { + for (k = 1; k < tcf_pedit_nkeys_locked(act); k++) { if (cmd != tcf_pedit_cmd(act, k)) { NL_SET_ERR_MSG_MOD(extack, "Unsupported pedit command offload"); return -EOPNOTSUPP; diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index b03dc4fe20e5..3b0a438977cd 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -3887,12 +3887,21 @@ int tc_setup_action(struct flow_action *flow_action, entry = &flow_action->entries[j]; spin_lock_bh(&act->tcfa_lock); + + /* Abort the offload if we have exhausted the allocated capacity */ + if (j >= flow_action->num_entries) { + NL_SET_ERR_MSG_MOD(extack, "Flow action buffer overflow"); + err = -ENOSPC; + goto err_out_locked; + } + err = tcf_act_get_user_cookie(entry, act); if (err) goto err_out_locked; - index = 0; - err = tc_setup_offload_act(act, entry, &index, extack); + index = flow_action->num_entries - j; + err = tc_setup_offload_act(act, entry, &index, + extack); if (err) goto err_out_locked; @@ -3946,10 +3955,13 @@ unsigned int tcf_exts_num_actions(struct tcf_exts *exts) int i; tcf_exts_for_each_action(i, act, exts) { - if (is_tcf_pedit(act)) - num_acts += tcf_pedit_nkeys(act); - else + if (is_tcf_pedit(act)) { + spin_lock_bh(&act->tcfa_lock); + num_acts += tcf_pedit_nkeys_locked(act); + spin_unlock_bh(&act->tcfa_lock); + } else { num_acts++; + } } return num_acts; } @@ -4050,6 +4062,9 @@ struct sk_buff *tcf_qevent_handle(struct tcf_qevent *qe, struct Qdisc *sch, stru skb_do_redirect(skb); *ret = __NET_XMIT_STOLEN; return NULL; + case TC_ACT_CONSUMED: + *ret = __NET_XMIT_STOLEN; + return NULL; } return skb; diff --git a/net/sched/cls_bpf.c b/net/sched/cls_bpf.c index a32754a2658b..e888d0aa9f21 100644 --- a/net/sched/cls_bpf.c +++ b/net/sched/cls_bpf.c @@ -142,7 +142,8 @@ static bool cls_bpf_is_ebpf(const struct cls_bpf_prog *prog) static int cls_bpf_offload_cmd(struct tcf_proto *tp, struct cls_bpf_prog *prog, struct cls_bpf_prog *oldprog, - struct netlink_ext_ack *extack) + struct netlink_ext_ack *extack, + bool is_rollback) { struct tcf_block *block = tp->chain->block; struct tc_cls_bpf_offload cls_bpf = {}; @@ -177,7 +178,8 @@ static int cls_bpf_offload_cmd(struct tcf_proto *tp, struct cls_bpf_prog *prog, &oldprog->in_hw_count, true); if (prog && err) { - cls_bpf_offload_cmd(tp, oldprog, prog, extack); + if (!is_rollback) + cls_bpf_offload_cmd(tp, oldprog, prog, extack, true); return err; } @@ -208,7 +210,7 @@ static int cls_bpf_offload(struct tcf_proto *tp, struct cls_bpf_prog *prog, if (!prog && !oldprog) return 0; - return cls_bpf_offload_cmd(tp, prog, oldprog, extack); + return cls_bpf_offload_cmd(tp, prog, oldprog, extack, false); } static void cls_bpf_stop_offload(struct tcf_proto *tp, @@ -217,7 +219,7 @@ static void cls_bpf_stop_offload(struct tcf_proto *tp, { int err; - err = cls_bpf_offload_cmd(tp, NULL, prog, extack); + err = cls_bpf_offload_cmd(tp, NULL, prog, extack, false); if (err) pr_err("Stopping hardware offload failed: %d\n", err); } diff --git a/net/sched/cls_flow.c b/net/sched/cls_flow.c index edf1252c1fde..9b472578bf75 100644 --- a/net/sched/cls_flow.c +++ b/net/sched/cls_flow.c @@ -21,6 +21,7 @@ #include <net/inet_sock.h> #include <net/pkt_cls.h> +#include <linux/siphash.h> #include <net/ip.h> #include <net/route.h> #include <net/flow_dissector.h> @@ -57,11 +58,15 @@ struct flow_filter { struct rcu_work rwork; }; +static siphash_aligned_key_t flow_keys_secret __read_mostly; + static inline u32 addr_fold(void *addr) { - unsigned long a = (unsigned long)addr; - - return (a & 0xFFFFFFFF) ^ (BITS_PER_LONG > 32 ? a >> 32 : 0); +#ifdef CONFIG_64BIT + return (u32)siphash_1u64((u64)addr, &flow_keys_secret); +#else + return (u32)siphash_1u32((u32)addr, &flow_keys_secret); +#endif } static u32 flow_get_src(const struct sk_buff *skb, const struct flow_keys *flow) @@ -596,6 +601,7 @@ static int flow_init(struct tcf_proto *tp) return -ENOBUFS; INIT_LIST_HEAD(&head->filters); rcu_assign_pointer(tp->root, head); + net_get_random_once(&flow_keys_secret, sizeof(flow_keys_secret)); return 0; } diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index f2ad946a519c..c19eeed7e7cf 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -1368,10 +1368,7 @@ static u32 cake_calc_overhead(struct cake_sched_data *q, u32 len, u32 off) if (q->min_netlen > len) q->min_netlen = len; - len += q->rate_overhead; - - if (len < q->rate_mpu) - len = q->rate_mpu; + len = max((s32)len + q->rate_overhead, (s32)q->rate_mpu); if (q->atm_mode == CAKE_ATM_ATM) { len += 47; diff --git a/net/sched/sch_codel.c b/net/sched/sch_codel.c index fa0314679e43..b284f8eda875 100644 --- a/net/sched/sch_codel.c +++ b/net/sched/sch_codel.c @@ -56,7 +56,7 @@ static void drop_func(struct sk_buff *skb, void *ctx) qdisc_qstats_drop(sch); } -static struct sk_buff *codel_qdisc_dequeue(struct Qdisc *sch) +static struct sk_buff *__codel_qdisc_dequeue(struct Qdisc *sch) { struct codel_sched_data *q = qdisc_priv(sch); struct sk_buff *skb; @@ -65,13 +65,51 @@ static struct sk_buff *codel_qdisc_dequeue(struct Qdisc *sch) &q->stats, qdisc_pkt_len, codel_get_enqueue_time, drop_func, dequeue_func); + if (skb) + qdisc_bstats_update(sch, skb); + return skb; +} + +static void codel_dequeue_drop(struct Qdisc *sch) +{ + struct codel_sched_data *q = qdisc_priv(sch); + if (q->stats.drop_count) { - qdisc_tree_reduce_backlog(sch, q->stats.drop_count, q->stats.drop_len); + qdisc_tree_reduce_backlog(sch, q->stats.drop_count, + q->stats.drop_len); q->stats.drop_count = 0; q->stats.drop_len = 0; } - if (skb) - qdisc_bstats_update(sch, skb); +} + +static struct sk_buff *codel_qdisc_dequeue(struct Qdisc *sch) +{ + struct sk_buff *skb; + + skb = __codel_qdisc_dequeue(sch); + + codel_dequeue_drop(sch); + + return skb; +} + +static struct sk_buff *codel_peek(struct Qdisc *sch) +{ + struct sk_buff *skb = skb_peek(&sch->gso_skb); + + if (!skb) { + skb = __codel_qdisc_dequeue(sch); + + if (skb) { + __skb_queue_head(&sch->gso_skb, skb); + /* it's still part of the queue */ + qdisc_qstats_backlog_inc(sch, skb); + sch->q.qlen++; + } + + codel_dequeue_drop(sch); + } + return skb; } @@ -256,7 +294,7 @@ static struct Qdisc_ops codel_qdisc_ops __read_mostly = { .enqueue = codel_qdisc_enqueue, .dequeue = codel_qdisc_dequeue, - .peek = qdisc_peek_dequeued, + .peek = codel_peek, .init = codel_init, .reset = codel_reset, .change = codel_change, diff --git a/net/sched/sch_drr.c b/net/sched/sch_drr.c index 9b6d79bd8737..44686fd1c274 100644 --- a/net/sched/sch_drr.c +++ b/net/sched/sch_drr.c @@ -270,7 +270,7 @@ static int drr_dump_class_stats(struct Qdisc *sch, unsigned long arg, memset(&xstats, 0, sizeof(xstats)); if (qlen) - xstats.deficit = cl->deficit; + xstats.deficit = READ_ONCE(cl->deficit); if (gnet_stats_copy_basic(d, NULL, &cl->bstats, true) < 0 || gnet_stats_copy_rate_est(d, &cl->rate_est) < 0 || @@ -362,7 +362,7 @@ static int drr_enqueue(struct sk_buff *skb, struct Qdisc *sch, if (!cl_is_active(cl)) { list_add_tail(&cl->alist, &q->active); - cl->deficit = cl->quantum; + WRITE_ONCE(cl->deficit, cl->quantum); } sch->qstats.backlog += len; @@ -389,7 +389,7 @@ static struct sk_buff *drr_dequeue(struct Qdisc *sch) len = qdisc_pkt_len(skb); if (len <= cl->deficit) { - cl->deficit -= len; + WRITE_ONCE(cl->deficit, cl->deficit - len); skb = qdisc_dequeue_peeked(cl->qdisc); if (unlikely(skb == NULL)) goto out; @@ -403,7 +403,7 @@ static struct sk_buff *drr_dequeue(struct Qdisc *sch) return skb; } - cl->deficit += cl->quantum; + WRITE_ONCE(cl->deficit, cl->deficit + cl->quantum); list_move_tail(&cl->alist, &q->active); } out: diff --git a/net/sched/sch_dualpi2.c b/net/sched/sch_dualpi2.c index 6beb6f5bbff1..f20a798e8f36 100644 --- a/net/sched/sch_dualpi2.c +++ b/net/sched/sch_dualpi2.c @@ -346,6 +346,8 @@ static int dualpi2_skb_classify(struct dualpi2_sched_data *q, struct tcf_proto *fl; int result; + cb->classified = DUALPI2_C_CLASSIC; + dualpi2_read_ect(skb); if (cb->ect & q->ecn_mask) { cb->classified = DUALPI2_C_L4S; @@ -359,10 +361,8 @@ static int dualpi2_skb_classify(struct dualpi2_sched_data *q, } fl = rcu_dereference_bh(q->tcf_filters); - if (!fl) { - cb->classified = DUALPI2_C_CLASSIC; + if (!fl) return NET_XMIT_SUCCESS; - } result = tcf_classify(skb, NULL, fl, &res, false); if (result >= 0) { @@ -579,7 +579,7 @@ static void drop_and_retry(struct dualpi2_sched_data *q, struct sk_buff *skb, qdisc_qstats_drop(sch); } -static struct sk_buff *dualpi2_qdisc_dequeue(struct Qdisc *sch) +static struct sk_buff *__dualpi2_qdisc_dequeue(struct Qdisc *sch) { struct dualpi2_sched_data *q = qdisc_priv(sch); struct sk_buff *skb; @@ -607,12 +607,49 @@ static struct sk_buff *dualpi2_qdisc_dequeue(struct Qdisc *sch) break; } + return skb; +} + +static void dualpi2_dequeue_drop(struct Qdisc *sch) +{ + struct dualpi2_sched_data *q = qdisc_priv(sch); + if (q->deferred_drops_cnt) { qdisc_tree_reduce_backlog(sch, q->deferred_drops_cnt, q->deferred_drops_len); q->deferred_drops_cnt = 0; q->deferred_drops_len = 0; } +} + +static struct sk_buff *dualpi2_qdisc_dequeue(struct Qdisc *sch) +{ + struct sk_buff *skb; + + skb = __dualpi2_qdisc_dequeue(sch); + + dualpi2_dequeue_drop(sch); + + return skb; +} + +static struct sk_buff *dualpi2_peek(struct Qdisc *sch) +{ + struct sk_buff *skb = skb_peek(&sch->gso_skb); + + if (!skb) { + skb = __dualpi2_qdisc_dequeue(sch); + + if (skb) { + __skb_queue_head(&sch->gso_skb, skb); + /* it's still part of the queue */ + qdisc_qstats_backlog_inc(sch, skb); + sch->q.qlen++; + } + + dualpi2_dequeue_drop(sch); + } + return skb; } @@ -1165,7 +1202,7 @@ static struct Qdisc_ops dualpi2_qdisc_ops __read_mostly = { .priv_size = sizeof(struct dualpi2_sched_data), .enqueue = dualpi2_qdisc_enqueue, .dequeue = dualpi2_qdisc_dequeue, - .peek = qdisc_peek_dequeued, + .peek = dualpi2_peek, .init = dualpi2_init, .destroy = dualpi2_destroy, .reset = dualpi2_reset, @@ -1174,6 +1211,7 @@ static struct Qdisc_ops dualpi2_qdisc_ops __read_mostly = { .dump_stats = dualpi2_dump_stats, .owner = THIS_MODULE, }; +MODULE_ALIAS_NET_SCH("dualpi2"); static int __init dualpi2_module_init(void) { diff --git a/net/sched/sch_fq_codel.c b/net/sched/sch_fq_codel.c index 90e1dfac6f59..3548985bbd42 100644 --- a/net/sched/sch_fq_codel.c +++ b/net/sched/sch_fq_codel.c @@ -279,7 +279,7 @@ static void drop_func(struct sk_buff *skb, void *ctx) qdisc_qstats_drop(sch); } -static struct sk_buff *fq_codel_dequeue(struct Qdisc *sch) +static struct sk_buff *__fq_codel_dequeue(struct Qdisc *sch) { struct fq_codel_sched_data *q = qdisc_priv(sch); struct sk_buff *skb; @@ -316,12 +316,49 @@ begin: qdisc_bstats_update(sch, skb); flow->deficit -= qdisc_pkt_len(skb); + return skb; +} + +static void fq_codel_dequeue_drop(struct Qdisc *sch) +{ + struct fq_codel_sched_data *q = qdisc_priv(sch); + if (q->cstats.drop_count) { qdisc_tree_reduce_backlog(sch, q->cstats.drop_count, q->cstats.drop_len); q->cstats.drop_count = 0; q->cstats.drop_len = 0; } +} + +static struct sk_buff *fq_codel_dequeue(struct Qdisc *sch) +{ + struct sk_buff *skb; + + skb = __fq_codel_dequeue(sch); + + fq_codel_dequeue_drop(sch); + + return skb; +} + +static struct sk_buff *fq_codel_peek(struct Qdisc *sch) +{ + struct sk_buff *skb = skb_peek(&sch->gso_skb); + + if (!skb) { + skb = __fq_codel_dequeue(sch); + + if (skb) { + __skb_queue_head(&sch->gso_skb, skb); + /* it's still part of the queue */ + qdisc_qstats_backlog_inc(sch, skb); + sch->q.qlen++; + } + + fq_codel_dequeue_drop(sch); + } + return skb; } @@ -723,7 +760,7 @@ static struct Qdisc_ops fq_codel_qdisc_ops __read_mostly = { .priv_size = sizeof(struct fq_codel_sched_data), .enqueue = fq_codel_enqueue, .dequeue = fq_codel_dequeue, - .peek = qdisc_peek_dequeued, + .peek = fq_codel_peek, .init = fq_codel_init, .reset = fq_codel_reset, .destroy = fq_codel_destroy, diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index 30d77ad7b81d..34573c9b8a5e 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -543,16 +543,24 @@ static void dev_watchdog(struct timer_list *t) dev->netdev_ops->ndo_tx_timeout(dev, i); netif_unfreeze_queues(dev); } - if (!mod_timer(&dev->watchdog_timer, - round_jiffies(oldest_start + - dev->watchdog_timeo))) - release = false; + spin_lock(&dev->watchdog_lock); + mod_timer(&dev->watchdog_timer, + round_jiffies(oldest_start + + dev->watchdog_timeo)); + release = false; + spin_unlock(&dev->watchdog_lock); } } spin_unlock(&dev->tx_global_lock); - if (release) + spin_lock(&dev->watchdog_lock); + if (timer_pending(&dev->watchdog_timer)) + release = false; + if (release && dev->watchdog_ref_held) { netdev_put(dev, &dev->watchdog_dev_tracker); + dev->watchdog_ref_held = false; + } + spin_unlock(&dev->watchdog_lock); } void netdev_watchdog_up(struct net_device *dev) @@ -561,18 +569,31 @@ void netdev_watchdog_up(struct net_device *dev) return; if (dev->watchdog_timeo <= 0) dev->watchdog_timeo = 5*HZ; + + spin_lock_bh(&dev->watchdog_lock); if (!mod_timer(&dev->watchdog_timer, - round_jiffies(jiffies + dev->watchdog_timeo))) - netdev_hold(dev, &dev->watchdog_dev_tracker, - GFP_ATOMIC); + round_jiffies(jiffies + dev->watchdog_timeo))) { + if (!dev->watchdog_ref_held) { + netdev_hold(dev, &dev->watchdog_dev_tracker, + GFP_ATOMIC); + dev->watchdog_ref_held = true; + } + } + spin_unlock_bh(&dev->watchdog_lock); } EXPORT_SYMBOL_GPL(netdev_watchdog_up); static void netdev_watchdog_down(struct net_device *dev) { netif_tx_lock_bh(dev); - if (timer_delete(&dev->watchdog_timer)) + + spin_lock(&dev->watchdog_lock); + if (timer_delete(&dev->watchdog_timer)) { netdev_put(dev, &dev->watchdog_dev_tracker); + dev->watchdog_ref_held = false; + } + spin_unlock(&dev->watchdog_lock); + netif_tx_unlock_bh(dev); } @@ -589,8 +610,6 @@ void netif_carrier_on(struct net_device *dev) return; atomic_inc(&dev->carrier_up_count); linkwatch_fire_event(dev); - if (netif_running(dev)) - netdev_watchdog_up(dev); } } EXPORT_SYMBOL(netif_carrier_on); diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c index 57221522fe56..601f99dd989a 100644 --- a/net/sched/sch_hfsc.c +++ b/net/sched/sch_hfsc.c @@ -715,7 +715,7 @@ init_vf(struct hfsc_class *cl, unsigned int len) rtsc_min(&cl->cl_virtual, &cl->cl_fsc, cl->cl_vt, cl->cl_total); cl->cl_vtadj = 0; - cl->cl_vtperiod++; /* increment vt period */ + WRITE_ONCE(cl->cl_vtperiod, cl->cl_vtperiod + 1); /* increment vt period */ cl->cl_parentperiod = cl->cl_parent->cl_vtperiod; if (cl->cl_parent->cl_nactive == 0) cl->cl_parentperiod++; @@ -753,11 +753,11 @@ update_vf(struct hfsc_class *cl, unsigned int len, u64 cur_time) u64 f; /* , myf_bound, delta; */ int go_passive = 0; - if (cl->qdisc->q.qlen == 0 && cl->cl_flags & HFSC_FSC) + if (cl->qdisc->q.qlen == 0 && cl->cl_flags & HFSC_FSC && cl->cl_nactive) go_passive = 1; for (; cl->cl_parent != NULL; cl = cl->cl_parent) { - cl->cl_total += len; + WRITE_ONCE(cl->cl_total, cl->cl_total + len); if (!(cl->cl_flags & HFSC_FSC) || cl->cl_nactive == 0) continue; @@ -847,7 +847,7 @@ hfsc_adjust_levels(struct hfsc_class *cl) if (p->level >= level) level = p->level + 1; } - cl->level = level; + WRITE_ONCE(cl->level, level); } while ((cl = cl->cl_parent) != NULL); } @@ -1338,10 +1338,10 @@ hfsc_dump_class_stats(struct Qdisc *sch, unsigned long arg, __u32 qlen; qdisc_qstats_qlen_backlog(cl->qdisc, &qlen, &cl->qstats.backlog); - xstats.level = cl->level; - xstats.period = cl->cl_vtperiod; - xstats.work = cl->cl_total; - xstats.rtwork = cl->cl_cumul; + xstats.level = READ_ONCE(cl->level); + xstats.period = READ_ONCE(cl->cl_vtperiod); + xstats.work = READ_ONCE(cl->cl_total); + xstats.rtwork = READ_ONCE(cl->cl_cumul); if (gnet_stats_copy_basic(d, NULL, &cl->bstats, true) < 0 || gnet_stats_copy_rate_est(d, &cl->rate_est) < 0 || @@ -1452,15 +1452,15 @@ hfsc_change_qdisc(struct Qdisc *sch, struct nlattr *opt, static void hfsc_reset_class(struct hfsc_class *cl) { - cl->cl_total = 0; - cl->cl_cumul = 0; + WRITE_ONCE(cl->cl_total, 0); + WRITE_ONCE(cl->cl_cumul, 0); cl->cl_d = 0; cl->cl_e = 0; cl->cl_vt = 0; cl->cl_vtadj = 0; cl->cl_cvtmin = 0; cl->cl_cvtoff = 0; - cl->cl_vtperiod = 0; + WRITE_ONCE(cl->cl_vtperiod, 0); cl->cl_parentperiod = 0; cl->cl_f = 0; cl->cl_myf = 0; @@ -1626,7 +1626,7 @@ hfsc_dequeue(struct Qdisc *sch) bstats_update(&cl->bstats, skb); update_vf(cl, qdisc_pkt_len(skb), cur_time); if (realtime) - cl->cl_cumul += qdisc_pkt_len(skb); + WRITE_ONCE(cl->cl_cumul, cl->cl_cumul + qdisc_pkt_len(skb)); if (cl->cl_flags & HFSC_RSC) { if (cl->qdisc->q.qlen != 0) { diff --git a/net/sched/sch_hhf.c b/net/sched/sch_hhf.c index 81c3a388c943..e1c9d1edc8a9 100644 --- a/net/sched/sch_hhf.c +++ b/net/sched/sch_hhf.c @@ -461,12 +461,39 @@ begin: return skb; } +static void hhf_reset_classifier(struct hhf_sched_data *q) +{ + int i; + + if (!q->hh_flows) + return; + + for (i = 0; i < HH_FLOWS_CNT; i++) { + struct hh_flow_state *flow, *next; + struct list_head *head = &q->hh_flows[i]; + + list_for_each_entry_safe(flow, next, head, flowchain) { + list_del(&flow->flowchain); + kfree(flow); + } + } + WRITE_ONCE(q->hh_flows_current_cnt, 0); + + for (i = 0; i < HHF_ARRAYS_CNT; i++) { + if (q->hhf_valid_bits[i]) + bitmap_zero(q->hhf_valid_bits[i], HHF_ARRAYS_LEN); + } + q->hhf_arrays_reset_timestamp = hhf_time_stamp(); +} + static void hhf_reset(struct Qdisc *sch) { + struct hhf_sched_data *q = qdisc_priv(sch); struct sk_buff *skb; while ((skb = hhf_dequeue(sch)) != NULL) rtnl_kfree_skbs(skb, skb); + hhf_reset_classifier(q); } static void hhf_destroy(struct Qdisc *sch) diff --git a/net/sched/sch_htb.c b/net/sched/sch_htb.c index b5e40c51655a..db97fdb40351 100644 --- a/net/sched/sch_htb.c +++ b/net/sched/sch_htb.c @@ -568,7 +568,7 @@ htb_change_class_mode(struct htb_sched *q, struct htb_class *cl, s64 *diff) if (new_mode == HTB_CANT_SEND) { cl->overlimits++; - q->overlimits++; + WRITE_ONCE(q->overlimits, q->overlimits + 1); } if (cl->prio_activity) { /* not necessary: speed optimization */ @@ -628,7 +628,7 @@ static int htb_enqueue(struct sk_buff *skb, struct Qdisc *sch, /* enqueue to helper queue */ if (q->direct_queue.qlen < q->direct_qlen) { __qdisc_enqueue_tail(skb, &q->direct_queue); - q->direct_pkts++; + WRITE_ONCE(q->direct_pkts, q->direct_pkts + 1); } else { return qdisc_drop(skb, sch, to_free); } @@ -1148,6 +1148,7 @@ static int htb_init(struct Qdisc *sch, struct nlattr *opt, * parts (especially calling ndo_setup_tc) on errors. */ q->offload = true; + sch->flags |= TCQ_F_OFFLOADED; return 0; } @@ -1208,17 +1209,12 @@ static int htb_dump(struct Qdisc *sch, struct sk_buff *skb) struct nlattr *nest; struct tc_htb_glob gopt; - if (q->offload) - sch->flags |= TCQ_F_OFFLOADED; - else - sch->flags &= ~TCQ_F_OFFLOADED; - - sch->qstats.overlimits = q->overlimits; + sch->qstats.overlimits = READ_ONCE(q->overlimits); /* Its safe to not acquire qdisc lock. As we hold RTNL, * no change can happen on the qdisc parameters. */ - gopt.direct_pkts = q->direct_pkts; + gopt.direct_pkts = READ_ONCE(q->direct_pkts); gopt.version = HTB_VER; gopt.rate2quantum = q->rate2quantum; gopt.defcls = q->defcls; diff --git a/net/sched/sch_multiq.c b/net/sched/sch_multiq.c index 06e03f5cd7ce..e50a37b7cec1 100644 --- a/net/sched/sch_multiq.c +++ b/net/sched/sch_multiq.c @@ -103,7 +103,7 @@ static struct sk_buff *multiq_dequeue(struct Qdisc *sch) if (!netif_xmit_stopped( netdev_get_tx_queue(qdisc_dev(sch), q->curband))) { qdisc = q->queues[q->curband]; - skb = qdisc->dequeue(qdisc); + skb = qdisc_dequeue_peeked(qdisc); if (skb) { qdisc_bstats_update(sch, skb); sch->q.qlen--; diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index b3481cafa6ec..99a711d0479c 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -748,7 +748,7 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, return NULL; skip_peek_checks: - skb = child->ops->dequeue(child); + skb = qdisc_dequeue_peeked(child); if (unlikely(!skb)) return NULL; diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c index ec4039a201a2..4d463ba5ce72 100644 --- a/net/sched/sch_teql.c +++ b/net/sched/sch_teql.c @@ -52,7 +52,8 @@ struct teql_master { struct Qdisc_ops qops; struct net_device *dev; - struct Qdisc *slaves; + struct Qdisc __rcu *slaves; + spinlock_t slaves_lock; /* serializes writes to ->slaves */ struct list_head master_list; unsigned long tx_bytes; unsigned long tx_packets; @@ -61,7 +62,7 @@ struct teql_master { }; struct teql_sched_data { - struct Qdisc *next; + struct Qdisc __rcu *next; struct teql_master *m; struct sk_buff_head q; }; @@ -101,7 +102,9 @@ teql_dequeue(struct Qdisc *sch) if (skb == NULL) { struct net_device *m = qdisc_dev(q); if (m) { - dat->m->slaves = sch; + spin_lock_bh(&dat->m->slaves_lock); + rcu_assign_pointer(dat->m->slaves, sch); + spin_unlock_bh(&dat->m->slaves_lock); netif_wake_queue(m); } } else { @@ -132,34 +135,49 @@ teql_destroy(struct Qdisc *sch) struct Qdisc *q, *prev; struct teql_sched_data *dat = qdisc_priv(sch); struct teql_master *master = dat->m; + struct netdev_queue *txq = NULL; + bool reset_master_queue = false; if (!master) return; - prev = master->slaves; + spin_lock_bh(&master->slaves_lock); + prev = rcu_dereference_protected(master->slaves, + lockdep_is_held(&master->slaves_lock)); if (prev) { do { - q = NEXT_SLAVE(prev); - if (q == sch) { - NEXT_SLAVE(prev) = NEXT_SLAVE(q); - if (q == master->slaves) { - master->slaves = NEXT_SLAVE(q); - if (q == master->slaves) { - struct netdev_queue *txq; - - txq = netdev_get_tx_queue(master->dev, 0); - master->slaves = NULL; - - dev_reset_queue(master->dev, - txq, NULL); - } - } - skb_queue_purge(&dat->q); - break; + struct Qdisc *head, *next; + + q = rcu_dereference_protected(NEXT_SLAVE(prev), + lockdep_is_held(&master->slaves_lock)); + if (q != sch) { + prev = q; + continue; } - } while ((prev = q) != master->slaves); + next = rcu_dereference_protected(NEXT_SLAVE(q), + lockdep_is_held(&master->slaves_lock)); + rcu_assign_pointer(NEXT_SLAVE(prev), next); + + head = rcu_dereference_protected(master->slaves, + lockdep_is_held(&master->slaves_lock)); + if (q == head) { + rcu_assign_pointer(master->slaves, next); + if (q == next) { + txq = netdev_get_tx_queue(master->dev, 0); + rcu_assign_pointer(master->slaves, NULL); + reset_master_queue = true; + } + } + skb_queue_purge(&dat->q); + break; + } while (prev != rcu_dereference_protected(master->slaves, + lockdep_is_held(&master->slaves_lock))); } + spin_unlock_bh(&master->slaves_lock); + + if (reset_master_queue) + dev_reset_queue(master->dev, txq, NULL); } static int teql_qdisc_init(struct Qdisc *sch, struct nlattr *opt, @@ -168,6 +186,7 @@ static int teql_qdisc_init(struct Qdisc *sch, struct nlattr *opt, struct net_device *dev = qdisc_dev(sch); struct teql_master *m = (struct teql_master *)sch->ops; struct teql_sched_data *q = qdisc_priv(sch); + struct Qdisc *first; if (dev->hard_header_len > m->dev->hard_header_len) return -EINVAL; @@ -184,7 +203,9 @@ static int teql_qdisc_init(struct Qdisc *sch, struct nlattr *opt, skb_queue_head_init(&q->q); - if (m->slaves) { + spin_lock_bh(&m->slaves_lock); + first = rcu_dereference_protected(m->slaves, lockdep_is_held(&m->slaves_lock)); + if (first) { if (m->dev->flags & IFF_UP) { if ((m->dev->flags & IFF_POINTOPOINT && !(dev->flags & IFF_POINTOPOINT)) || @@ -192,8 +213,10 @@ static int teql_qdisc_init(struct Qdisc *sch, struct nlattr *opt, !(dev->flags & IFF_BROADCAST)) || (m->dev->flags & IFF_MULTICAST && !(dev->flags & IFF_MULTICAST)) || - dev->mtu < m->dev->mtu) + dev->mtu < m->dev->mtu) { + spin_unlock_bh(&m->slaves_lock); return -EINVAL; + } } else { if (!(dev->flags&IFF_POINTOPOINT)) m->dev->flags &= ~IFF_POINTOPOINT; @@ -204,14 +227,17 @@ static int teql_qdisc_init(struct Qdisc *sch, struct nlattr *opt, if (dev->mtu < m->dev->mtu) m->dev->mtu = dev->mtu; } - q->next = NEXT_SLAVE(m->slaves); - NEXT_SLAVE(m->slaves) = sch; + rcu_assign_pointer(q->next, + rcu_dereference_protected(NEXT_SLAVE(first), + lockdep_is_held(&m->slaves_lock))); + rcu_assign_pointer(NEXT_SLAVE(first), sch); } else { - q->next = sch; - m->slaves = sch; + rcu_assign_pointer(q->next, sch); + rcu_assign_pointer(m->slaves, sch); m->dev->mtu = dev->mtu; m->dev->flags = (m->dev->flags&~FMASK)|(dev->flags&FMASK); } + spin_unlock_bh(&m->slaves_lock); return 0; } @@ -285,12 +311,14 @@ static netdev_tx_t teql_master_xmit(struct sk_buff *skb, struct net_device *dev) int subq = skb_get_queue_mapping(skb); struct sk_buff *skb_res = NULL; - start = master->slaves; - restart: nores = 0; busy = 0; + rcu_read_lock(); + + start = rcu_dereference(master->slaves); + q = start; if (!q) goto drop; @@ -317,10 +345,17 @@ restart: netdev_start_xmit(skb, slave, slave_txq, false) == NETDEV_TX_OK) { __netif_tx_unlock(slave_txq); - master->slaves = NEXT_SLAVE(q); + spin_lock(&master->slaves_lock); + if (rcu_dereference_protected(master->slaves, + lockdep_is_held(&master->slaves_lock)) == q) + rcu_assign_pointer(master->slaves, + rcu_dereference_protected(NEXT_SLAVE(q), + lockdep_is_held(&master->slaves_lock))); + spin_unlock(&master->slaves_lock); netif_wake_queue(dev); master->tx_packets++; master->tx_bytes += length; + rcu_read_unlock(); return NETDEV_TX_OK; } __netif_tx_unlock(slave_txq); @@ -329,45 +364,56 @@ restart: busy = 1; break; case 1: - master->slaves = NEXT_SLAVE(q); + spin_lock(&master->slaves_lock); + if (rcu_dereference_protected(master->slaves, + lockdep_is_held(&master->slaves_lock)) == q) + rcu_assign_pointer(master->slaves, + rcu_dereference_protected(NEXT_SLAVE(q), + lockdep_is_held(&master->slaves_lock))); + spin_unlock(&master->slaves_lock); + rcu_read_unlock(); return NETDEV_TX_OK; default: nores = 1; break; } __skb_pull(skb, skb_network_offset(skb)); - } while ((q = NEXT_SLAVE(q)) != start); + } while ((q = rcu_dereference(NEXT_SLAVE(q))) != start); if (nores && skb_res == NULL) { skb_res = skb; + rcu_read_unlock(); goto restart; } if (busy) { netif_stop_queue(dev); + rcu_read_unlock(); return NETDEV_TX_BUSY; } master->tx_errors++; drop: master->tx_dropped++; + rcu_read_unlock(); dev_kfree_skb(skb); return NETDEV_TX_OK; } static int teql_master_open(struct net_device *dev) { - struct Qdisc *q; + struct Qdisc *q, *first; struct teql_master *m = netdev_priv(dev); int mtu = 0xFFFE; unsigned int flags = IFF_NOARP | IFF_MULTICAST; - if (m->slaves == NULL) + first = rtnl_dereference(m->slaves); + if (!first) return -EUNATCH; flags = FMASK; - q = m->slaves; + q = first; do { struct net_device *slave = qdisc_dev(q); @@ -389,7 +435,7 @@ static int teql_master_open(struct net_device *dev) flags &= ~IFF_BROADCAST; if (!(slave->flags&IFF_MULTICAST)) flags &= ~IFF_MULTICAST; - } while ((q = NEXT_SLAVE(q)) != m->slaves); + } while ((q = rtnl_dereference(NEXT_SLAVE(q))) != first); m->dev->mtu = mtu; m->dev->flags = (m->dev->flags&~FMASK) | flags; @@ -417,14 +463,15 @@ static void teql_master_stats64(struct net_device *dev, static int teql_master_mtu(struct net_device *dev, int new_mtu) { struct teql_master *m = netdev_priv(dev); - struct Qdisc *q; + struct Qdisc *q, *first; - q = m->slaves; + first = rtnl_dereference(m->slaves); + q = first; if (q) { do { if (new_mtu > qdisc_dev(q)->mtu) return -EINVAL; - } while ((q = NEXT_SLAVE(q)) != m->slaves); + } while ((q = rtnl_dereference(NEXT_SLAVE(q))) != first); } WRITE_ONCE(dev->mtu, new_mtu); @@ -444,6 +491,7 @@ static __init void teql_master_setup(struct net_device *dev) struct teql_master *master = netdev_priv(dev); struct Qdisc_ops *ops = &master->qops; + spin_lock_init(&master->slaves_lock); master->dev = dev; ops->priv_size = sizeof(struct teql_sched_data); diff --git a/net/sctp/diag.c b/net/sctp/diag.c index d758f5c3e06e..c2a0de2adf6f 100644 --- a/net/sctp/diag.c +++ b/net/sctp/diag.c @@ -92,6 +92,7 @@ static int inet_diag_msg_sctpladdrs_fill(struct sk_buff *skb, if (!--addrcnt) break; } + WARN_ON_ONCE(addrcnt); rcu_read_unlock(); return 0; @@ -373,42 +374,39 @@ static int sctp_ep_dump(struct sctp_endpoint *ep, void *p) struct sk_buff *skb = commp->skb; struct netlink_callback *cb = commp->cb; const struct inet_diag_req_v2 *r = commp->r; - struct net *net = sock_net(skb->sk); struct inet_sock *inet = inet_sk(sk); int err = 0; - if (!net_eq(sock_net(sk), net)) + lock_sock(sk); + if (ep->base.dead) goto out; - if (cb->args[4] < cb->args[1]) - goto next; - - if (!(r->idiag_states & TCPF_LISTEN) && !list_empty(&ep->asocs)) - goto next; + /* Skip eps with assocs if non-LISTEN states were requested, since + * they'll be dumped by sctp_sock_dump() during assoc traversal. + */ + if ((r->idiag_states & ~(TCPF_LISTEN | TCPF_CLOSE)) && + !list_empty(&ep->asocs)) + goto out; if (r->sdiag_family != AF_UNSPEC && sk->sk_family != r->sdiag_family) - goto next; + goto out; if (r->id.idiag_sport != inet->inet_sport && r->id.idiag_sport) - goto next; + goto out; if (r->id.idiag_dport != inet->inet_dport && r->id.idiag_dport) - goto next; - - if (inet_sctp_diag_fill(sk, NULL, skb, r, - sk_user_ns(NETLINK_CB(cb->skb).sk), - NETLINK_CB(cb->skb).portid, - cb->nlh->nlmsg_seq, NLM_F_MULTI, - cb->nlh, commp->net_admin) < 0) { - err = 2; goto out; - } -next: - cb->args[4]++; + + err = inet_sctp_diag_fill(sk, NULL, skb, r, + sk_user_ns(NETLINK_CB(cb->skb).sk), + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + cb->nlh, commp->net_admin); out: + release_sock(sk); return err; } @@ -479,41 +477,40 @@ static void sctp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb, .r = r, .net_admin = netlink_net_capable(cb->skb, CAP_NET_ADMIN), }; - int pos = cb->args[2]; + int pos; /* eps hashtable dumps * args: * 0 : if it will traversal listen sock * 1 : to record the sock pos of this time's traversal - * 4 : to work as a temporary variable to traversal list */ if (cb->args[0] == 0) { - if (!(idiag_states & TCPF_LISTEN)) - goto skip; - if (sctp_for_each_endpoint(sctp_ep_dump, &commp)) - goto done; -skip: + if (idiag_states & TCPF_LISTEN) { + pos = cb->args[1]; + if (sctp_for_each_endpoint(sctp_ep_dump, net, &pos, + &commp)) { + cb->args[1] = pos; + return; + } + } cb->args[0] = 1; cb->args[1] = 0; - cb->args[4] = 0; } + if (!(idiag_states & ~(TCPF_LISTEN | TCPF_CLOSE))) + return; + /* asocs by transport hashtable dump * args: * 1 : to record the assoc pos of this time's traversal * 2 : to record the transport pos of this time's traversal * 3 : to mark if we have dumped the ep info of the current asoc - * 4 : to work as a temporary variable to traversal list - * 5 : to save the sk we get from travelsing the tsp list. + * 4 : to track position within ep->asocs list in sctp_sock_dump() */ - if (!(idiag_states & ~(TCPF_LISTEN | TCPF_CLOSE))) - goto done; - + pos = cb->args[2]; sctp_transport_traverse_process(sctp_sock_filter, sctp_sock_dump, net, &pos, &commp); cb->args[2] = pos; - -done: cb->args[1] = cb->args[4]; cb->args[4] = 0; } diff --git a/net/sctp/protocol.c b/net/sctp/protocol.c index 6ce58fc95ef5..bbeff25185b1 100644 --- a/net/sctp/protocol.c +++ b/net/sctp/protocol.c @@ -695,8 +695,9 @@ static void sctp_free_addr_wq(struct net *net) struct sctp_sockaddr_entry *addrw; struct sctp_sockaddr_entry *temp; + timer_shutdown_sync(&net->sctp.addr_wq_timer); + spin_lock_bh(&net->sctp.addr_wq_lock); - timer_delete(&net->sctp.addr_wq_timer); list_for_each_entry_safe(addrw, temp, &net->sctp.addr_waitq, list) { list_del(&addrw->list); kfree(addrw); diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c index 51affa4fd396..a7804e47d382 100644 --- a/net/sctp/sm_make_chunk.c +++ b/net/sctp/sm_make_chunk.c @@ -1761,6 +1761,8 @@ struct sctp_association *sctp_unpack_cookie( bear_cookie = &cookie->c; ch = (struct sctp_chunkhdr *)(bear_cookie + 1); + if (ch->type != SCTP_CID_INIT) + goto malformed; chlen = ntohs(ch->length); if (chlen < sizeof(struct sctp_init_chunk)) goto malformed; @@ -2298,7 +2300,8 @@ int sctp_verify_init(struct net *net, const struct sctp_endpoint *ep, * VIOLATION error. We build the ERROR chunk here and let the normal * error handling code build and send the packet. */ - if (param.v != (void *)chunk->chunk_end) + if (param.v != (void *)peer_init + + SCTP_PAD4(ntohs(peer_init->chunk_hdr.length))) return sctp_process_inv_paramlength(asoc, param.p, chunk, errp); /* The only missing mandatory param possible today is @@ -2641,6 +2644,9 @@ do_addr_param: goto fall_through; addr_param = param.v + sizeof(struct sctp_addip_param); + if (ntohs(addr_param->p.length) > + ntohs(param.p->length) - sizeof(struct sctp_addip_param)) + break; af = sctp_get_af_specific(param_type2af(addr_param->p.type)); if (!af) @@ -3039,13 +3045,16 @@ static __be16 sctp_process_asconf_param(struct sctp_association *asoc, union sctp_addr addr; struct sctp_af *af; - addr_param = (void *)asconf_param + sizeof(*asconf_param); - if (asconf_param->param_hdr.type != SCTP_PARAM_ADD_IP && asconf_param->param_hdr.type != SCTP_PARAM_DEL_IP && asconf_param->param_hdr.type != SCTP_PARAM_SET_PRIMARY) return SCTP_ERROR_UNKNOWN_PARAM; + addr_param = (void *)asconf_param + sizeof(*asconf_param); + if (ntohs(addr_param->p.length) > + ntohs(asconf_param->param_hdr.length) - sizeof(*asconf_param)) + return SCTP_ERROR_PROTO_VIOLATION; + switch (addr_param->p.type) { case SCTP_PARAM_IPV6_ADDRESS: if (!asoc->peer.ipv6_address) diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index 9b23c11cbb9e..abfd72d1f20c 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c @@ -74,7 +74,8 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale( const struct sctp_association *asoc, const union sctp_subtype type, void *arg, - struct sctp_cmd_seq *commands); + struct sctp_cmd_seq *commands, + struct sctp_errhdr *err); static enum sctp_disposition sctp_sf_shut_8_4_5( struct net *net, const struct sctp_endpoint *ep, @@ -705,11 +706,12 @@ enum sctp_disposition sctp_sf_do_5_1D_ce(struct net *net, struct sctp_cmd_seq *commands) { struct sctp_ulpevent *ev, *ai_ev = NULL, *auth_ev = NULL; + struct sctp_chunk *err_chk_p = NULL; struct sctp_association *new_asoc; struct sctp_init_chunk *peer_init; struct sctp_chunk *chunk = arg; - struct sctp_chunk *err_chk_p; struct sctp_chunk *repl; + enum sctp_cid cid; struct sock *sk; int error = 0; @@ -783,6 +785,19 @@ enum sctp_disposition sctp_sf_do_5_1D_ce(struct net *net, } } + peer_init = (struct sctp_init_chunk *)(chunk->subh.cookie_hdr + 1); + cid = peer_init->chunk_hdr.type; + if (!sctp_sk(sk)->cookie_auth_enable && + !sctp_verify_init(net, ep, asoc, cid, peer_init, chunk, + &err_chk_p)) { + sctp_association_free(new_asoc); + if (err_chk_p) + sctp_chunk_free(err_chk_p); + return sctp_sf_pdiscard(net, ep, asoc, type, arg, commands); + } + if (err_chk_p) + sctp_chunk_free(err_chk_p); + if (security_sctp_assoc_request(new_asoc, chunk->head_skb ?: chunk->skb)) { sctp_association_free(new_asoc); return sctp_sf_pdiscard(net, ep, asoc, type, arg, commands); @@ -796,7 +811,6 @@ enum sctp_disposition sctp_sf_do_5_1D_ce(struct net *net, /* This is a brand-new association, so these are not yet side * effects--it is safe to run them here. */ - peer_init = (struct sctp_init_chunk *)(chunk->subh.cookie_hdr + 1); if (!sctp_process_init(new_asoc, chunk, &chunk->subh.cookie_hdr->c.peer_addr, peer_init, GFP_ATOMIC)) @@ -2210,10 +2224,12 @@ enum sctp_disposition sctp_sf_do_5_2_4_dupcook( void *arg, struct sctp_cmd_seq *commands) { + struct sctp_chunk *err_chk_p = NULL; struct sctp_association *new_asoc; + struct sctp_init_chunk *peer_init; struct sctp_chunk *chunk = arg; enum sctp_disposition retval; - struct sctp_chunk *err_chk_p; + enum sctp_cid cid; int error = 0; char action; @@ -2282,6 +2298,21 @@ enum sctp_disposition sctp_sf_do_5_2_4_dupcook( switch (action) { case 'A': /* Association restart. */ case 'B': /* Collision case B. */ + peer_init = (struct sctp_init_chunk *) + (chunk->subh.cookie_hdr + 1); + cid = peer_init->chunk_hdr.type; + if (!sctp_sk(ep->base.sk)->cookie_auth_enable && + !sctp_verify_init(net, ep, asoc, cid, peer_init, chunk, + &err_chk_p)) { + sctp_association_free(new_asoc); + if (err_chk_p) + sctp_chunk_free(err_chk_p); + return sctp_sf_pdiscard(net, ep, asoc, type, arg, + commands); + } + if (err_chk_p) + sctp_chunk_free(err_chk_p); + fallthrough; case 'D': /* Collision case D. */ /* Update socket peer label if first association. */ if (security_sctp_assoc_request((struct sctp_association *)asoc, @@ -2494,9 +2525,15 @@ enum sctp_disposition sctp_sf_cookie_echoed_err( * errors. */ sctp_walk_errors(err, chunk->chunk_hdr) { - if (SCTP_ERROR_STALE_COOKIE == err->cause) - return sctp_sf_do_5_2_6_stale(net, ep, asoc, type, - arg, commands); + if (err->cause != SCTP_ERROR_STALE_COOKIE) + continue; + /* The staleness is only meaningful if the cause is long + * enough to hold it; a shorter one is malformed. + */ + if (ntohs(err->length) < sizeof(*err) + sizeof(__be32)) + break; + return sctp_sf_do_5_2_6_stale(net, ep, asoc, type, + arg, commands, err); } /* It is possible to have malformed error causes, and that @@ -2538,13 +2575,13 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale( const struct sctp_association *asoc, const union sctp_subtype type, void *arg, - struct sctp_cmd_seq *commands) + struct sctp_cmd_seq *commands, + struct sctp_errhdr *err) { int attempts = asoc->init_err_counter + 1; - struct sctp_chunk *chunk = arg, *reply; struct sctp_cookie_preserve_param bht; struct sctp_bind_addr *bp; - struct sctp_errhdr *err; + struct sctp_chunk *reply; u32 stale; if (attempts > asoc->max_init_attempts) { @@ -2555,8 +2592,6 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale( return SCTP_DISPOSITION_DELETE_TCB; } - err = (struct sctp_errhdr *)(chunk->skb->data); - /* When calculating the time extension, an implementation * SHOULD use the RTT information measured based on the * previous COOKIE ECHO / ERROR exchange, and should add no diff --git a/net/sctp/socket.c b/net/sctp/socket.c index c763eb3296b3..b4f706a975be 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c @@ -2095,7 +2095,7 @@ static int sctp_skb_pull(struct sk_buff *skb, int len) * 5 for complete description of the flags. */ static int sctp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { struct sctp_ulpevent *event = NULL; struct sctp_sock *sp = sctp_sk(sk); @@ -2104,11 +2104,11 @@ static int sctp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int err = 0; int skb_len; - pr_debug("%s: sk:%p, msghdr:%p, len:%zd, flags:0x%x, addr_len:%p)\n", - __func__, sk, msg, len, flags, addr_len); + pr_debug("%s: sk:%p, msghdr:%p, len:%zd, flags:0x%x)\n", + __func__, sk, msg, len, flags); if (unlikely(flags & MSG_ERRQUEUE)) - return inet_recv_error(sk, msg, len, addr_len); + return inet_recv_error(sk, msg, len); if (sk_can_busy_loop(sk) && skb_queue_empty_lockless(&sk->sk_receive_queue)) @@ -2149,9 +2149,9 @@ static int sctp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, sock_recv_cmsgs(msg, sk, head_skb); if (sctp_ulpevent_is_notification(event)) { msg->msg_flags |= MSG_NOTIFICATION; - sp->pf->event_msgname(event, msg->msg_name, addr_len); + sp->pf->event_msgname(event, msg->msg_name, &msg->msg_namelen); } else { - sp->pf->skb_msgname(head_skb, msg->msg_name, addr_len); + sp->pf->skb_msgname(head_skb, msg->msg_name, &msg->msg_namelen); } /* Check if we allow SCTP_NXTINFO. */ @@ -4110,8 +4110,9 @@ static int sctp_setsockopt_reset_streams(struct sock *sk, if (optlen < sizeof(*params)) return -EINVAL; /* srs_number_streams is u16, so optlen can't be bigger than this. */ - optlen = min_t(unsigned int, optlen, USHRT_MAX + - sizeof(__u16) * sizeof(*params)); + optlen = min_t(unsigned int, optlen, + struct_size_t(struct sctp_reset_streams, srs_stream_list, + USHRT_MAX)); if (params->srs_number_streams * sizeof(__u16) > optlen - sizeof(*params)) @@ -4597,8 +4598,8 @@ static int sctp_setsockopt(struct sock *sk, int level, int optname, if (optlen > 0) { /* Trim it to the biggest size sctp sockopt may need if necessary */ optlen = min_t(unsigned int, optlen, - PAGE_ALIGN(USHRT_MAX + - sizeof(__u16) * sizeof(struct sctp_reset_streams))); + PAGE_ALIGN(struct_size_t(struct sctp_reset_streams, + srs_stream_list, USHRT_MAX))); kopt = memdup_sockptr(optval, optlen); if (IS_ERR(kopt)) return PTR_ERR(kopt); @@ -5310,24 +5311,39 @@ struct sctp_transport *sctp_transport_get_idx(struct net *net, } int sctp_for_each_endpoint(int (*cb)(struct sctp_endpoint *, void *), - void *p) { - int err = 0; - int hash = 0; - struct sctp_endpoint *ep; + struct net *net, int *pos, void *p) { + int err, hash = 0, idx = 0, start; struct sctp_hashbucket *head; + struct sctp_endpoint *ep; for (head = sctp_ep_hashtable; hash < sctp_ep_hashsize; hash++, head++) { + start = idx; +again: read_lock_bh(&head->lock); sctp_for_each_hentry(ep, &head->chain) { - err = cb(ep, p); - if (err) + if (sock_net(ep->base.sk) != net) + continue; + if (idx++ >= *pos) { + sctp_endpoint_hold(ep); break; + } } read_unlock_bh(&head->lock); + + if (ep) { + err = cb(ep, p); + sctp_endpoint_put(ep); + if (err) + return err; + (*pos)++; + + idx = start; + goto again; + } } - return err; + return 0; } EXPORT_SYMBOL_GPL(sctp_for_each_endpoint); diff --git a/net/smc/smc_cdc.c b/net/smc/smc_cdc.c index 619b3bab3824..32d6d03df321 100644 --- a/net/smc/smc_cdc.c +++ b/net/smc/smc_cdc.c @@ -470,9 +470,9 @@ static void smc_cdc_rx_handler(struct ib_wc *wc, void *buf) { struct smc_link *link = (struct smc_link *)wc->qp->qp_context; struct smc_cdc_msg *cdc = buf; + struct smc_sock *smc = NULL; struct smc_connection *conn; struct smc_link_group *lgr; - struct smc_sock *smc; if (wc->byte_len < offsetof(struct smc_cdc_msg, reserved)) return; /* short message */ @@ -483,21 +483,26 @@ static void smc_cdc_rx_handler(struct ib_wc *wc, void *buf) lgr = smc_get_lgr(link); read_lock_bh(&lgr->conns_lock); conn = smc_lgr_find_conn(ntohl(cdc->token), lgr); - read_unlock_bh(&lgr->conns_lock); - if (!conn || conn->out_of_sync) + if (!conn || conn->out_of_sync) { + read_unlock_bh(&lgr->conns_lock); return; + } smc = container_of(conn, struct smc_sock, conn); + sock_hold(&smc->sk); + read_unlock_bh(&lgr->conns_lock); if (cdc->prod_flags.failover_validation) { smc_cdc_msg_validate(smc, cdc, link); - return; + goto out; } if (smc_cdc_before(ntohs(cdc->seqno), conn->local_rx_ctrl.seqno)) /* received seqno is old */ - return; + goto out; smc_cdc_msg_recv(smc, cdc); +out: + sock_put(&smc->sk); } static struct smc_wr_rx_handler smc_cdc_rx_handlers[] = { diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c index 8ca354ecfd02..a142e20ce7e7 100644 --- a/net/sunrpc/clnt.c +++ b/net/sunrpc/clnt.c @@ -1026,8 +1026,23 @@ rpc_free_auth(struct rpc_clnt *clnt) return NULL; } -/* - * Release reference to the RPC client +/** + * rpc_hold_client - acquire a reference on an rpc_clnt + * @clnt: rpc_clnt to pin + * + * Pairs with rpc_release_client(). + */ +void rpc_hold_client(struct rpc_clnt *clnt) +{ + refcount_inc(&clnt->cl_count); +} + +/** + * rpc_release_client - release a reference on an rpc_clnt + * @clnt: rpc_clnt to release + * + * Pairs with rpc_hold_client(). The rpc_clnt's resources are + * freed once its reference count drops to zero. */ void rpc_release_client(struct rpc_clnt *clnt) diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c index 4704dce7284e..1f4bf5b290e4 100644 --- a/net/sunrpc/svc.c +++ b/net/sunrpc/svc.c @@ -1529,6 +1529,12 @@ static void svc_release_rqst(struct svc_rqst *rqstp) if (procp && procp->pc_release) procp->pc_release(rqstp); + + /* + * A subsequent svc_release_rqst() on this rqstp must not + * re-invoke pc_release against released state. + */ + rqstp->rq_procinfo = NULL; } /** @@ -1547,6 +1553,9 @@ void svc_process(struct svc_rqst *rqstp) svc_xprt_deferred_close(rqstp->rq_xprt); #endif + /* Discard a stale release hook from a previous RPC. */ + rqstp->rq_procinfo = NULL; + /* * Setup response xdr_buf. * Initially it has just one page @@ -1603,6 +1612,7 @@ void svc_process_bc(struct rpc_rqst *req, struct svc_rqst *rqstp) int proc_error; /* Build the svc_rqst used by the common processing routine */ + rqstp->rq_procinfo = NULL; rqstp->rq_xid = req->rq_xid; rqstp->rq_prot = req->rq_xprt->prot; rqstp->rq_bc_net = req->rq_xprt->xprt_net; diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c index 7b90abc5cf0e..9c14558bc552 100644 --- a/net/sunrpc/svcsock.c +++ b/net/sunrpc/svcsock.c @@ -462,6 +462,7 @@ static void svc_tcp_handshake_done(void *data, int status, key_serial_t peerid) } clear_bit(XPT_HANDSHAKE, &xprt->xpt_flags); complete_all(&svsk->sk_handshake_done); + svc_xprt_put(xprt); } /** @@ -485,9 +486,13 @@ static void svc_tcp_handshake(struct svc_xprt *xprt) clear_bit(XPT_TLS_SESSION, &xprt->xpt_flags); init_completion(&svsk->sk_handshake_done); + /* Pin the transport across the asynchronous handshake callback. */ + svc_xprt_get(xprt); + ret = tls_server_hello_x509(&args, GFP_KERNEL); if (ret) { trace_svc_tls_not_started(xprt); + svc_xprt_put(xprt); goto out_failed; } @@ -496,8 +501,13 @@ static void svc_tcp_handshake(struct svc_xprt *xprt) if (ret <= 0) { if (tls_handshake_cancel(sk)) { trace_svc_tls_timed_out(xprt); + svc_xprt_put(xprt); goto out_close; } + /* Cancellation lost to handshake_complete(): the + * callback is in flight and should finish quickly. + */ + wait_for_completion(&svsk->sk_handshake_done); } if (!test_bit(XPT_TLS_SESSION, &xprt->xpt_flags)) { diff --git a/net/sunrpc/sysfs.c b/net/sunrpc/sysfs.c index 8b01b7ae2690..9dcd5e985e72 100644 --- a/net/sunrpc/sysfs.c +++ b/net/sunrpc/sysfs.c @@ -326,7 +326,7 @@ static ssize_t rpc_sysfs_xprt_switch_add_xprt_store(struct kobject *kobj, { struct rpc_xprt_switch *xprt_switch = rpc_sysfs_xprt_switch_kobj_get_xprt(kobj); - struct xprt_create xprt_create_args; + struct xprt_create xprt_create_args = {}; struct rpc_xprt *xprt, *new; if (!xprt_switch) @@ -347,7 +347,7 @@ static ssize_t rpc_sysfs_xprt_switch_add_xprt_store(struct kobject *kobj, xprt_create_args.reconnect_timeout = xprt->max_reconnect_timeout; new = xprt_create_transport(&xprt_create_args); - if (IS_ERR_OR_NULL(new)) { + if (IS_ERR(new)) { count = PTR_ERR(new); goto out_put_xprt; } diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c index 70efc727a9cd..fe17a6e8372b 100644 --- a/net/sunrpc/xdr.c +++ b/net/sunrpc/xdr.c @@ -152,6 +152,8 @@ unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, unsigned int count = 0; if (head->iov_len) { + if (unlikely(count >= bvec_size)) + goto bvec_overflow; bvec_set_virt(bvec++, head->iov_base, head->iov_len); ++count; } @@ -165,25 +167,27 @@ unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, while (remaining > 0) { len = min_t(unsigned int, remaining, PAGE_SIZE - offset); + if (unlikely(count >= bvec_size)) + goto bvec_overflow; bvec_set_page(bvec++, *pages++, len, offset); remaining -= len; offset = 0; - if (unlikely(++count > bvec_size)) - goto bvec_overflow; + ++count; } } if (tail->iov_len) { - bvec_set_virt(bvec, tail->iov_base, tail->iov_len); - if (unlikely(++count > bvec_size)) + if (unlikely(count >= bvec_size)) goto bvec_overflow; + bvec_set_virt(bvec, tail->iov_base, tail->iov_len); + ++count; } return count; bvec_overflow: pr_warn_once("%s: bio_vec array overflow\n", __func__); - return count - 1; + return count; } EXPORT_SYMBOL_GPL(xdr_buf_to_bvec); diff --git a/net/sunrpc/xprt.c b/net/sunrpc/xprt.c index 1023361845f9..59b45aaadda2 100644 --- a/net/sunrpc/xprt.c +++ b/net/sunrpc/xprt.c @@ -1663,6 +1663,22 @@ void xprt_add_backlog(struct rpc_xprt *xprt, struct rpc_task *task) } EXPORT_SYMBOL_GPL(xprt_add_backlog); +/** + * xprt_add_backlog_noncongested - queue task on backlog + * @xprt: transport whose backlog queue receives the task + * @task: task to queue + * + * Like xprt_add_backlog, but does not set XPRT_CONGESTED. + * For transports whose free_slot path does not synchronize + * with xprt_throttle_congested via reserve_lock. + */ +void xprt_add_backlog_noncongested(struct rpc_xprt *xprt, + struct rpc_task *task) +{ + rpc_sleep_on(&xprt->backlog, task, xprt_complete_request_init); +} +EXPORT_SYMBOL_GPL(xprt_add_backlog_noncongested); + static bool __xprt_set_rq(struct rpc_task *task, void *data) { struct rpc_rqst *req = data; diff --git a/net/sunrpc/xprtrdma/backchannel.c b/net/sunrpc/xprtrdma/backchannel.c index 8c817e755262..02ffe7e1b5ed 100644 --- a/net/sunrpc/xprtrdma/backchannel.c +++ b/net/sunrpc/xprtrdma/backchannel.c @@ -158,9 +158,7 @@ void xprt_rdma_bc_free_rqst(struct rpc_rqst *rqst) rpcrdma_rep_put(&r_xprt->rx_buf, rep); req->rl_reply = NULL; - spin_lock(&xprt->bc_pa_lock); - list_add_tail(&rqst->rq_bc_pa_list, &xprt->bc_pa_list); - spin_unlock(&xprt->bc_pa_lock); + rpcrdma_req_put(req); xprt_put(xprt); } @@ -202,6 +200,7 @@ create_req: rqst->rq_xprt = xprt; __set_bit(RPC_BC_PA_IN_USE, &rqst->rq_bc_pa_state); xdr_buf_init(&rqst->rq_snd_buf, rdmab_data(req->rl_sendbuf), size); + kref_init(&req->rl_kref); return rqst; } diff --git a/net/sunrpc/xprtrdma/frwr_ops.c b/net/sunrpc/xprtrdma/frwr_ops.c index 31434aeb8e29..a82eefe5e6b5 100644 --- a/net/sunrpc/xprtrdma/frwr_ops.c +++ b/net/sunrpc/xprtrdma/frwr_ops.c @@ -404,7 +404,7 @@ int frwr_send(struct rpcrdma_xprt *r_xprt, struct rpcrdma_req *req) ++num_wrs; } - if ((kref_read(&req->rl_kref) > 1) || num_wrs > ep->re_send_count) { + if (req->rl_sendctx->sc_unmap_count || num_wrs > ep->re_send_count) { send_wr->send_flags |= IB_SEND_SIGNALED; ep->re_send_count = min_t(unsigned int, ep->re_send_batch, num_wrs - ep->re_send_count); diff --git a/net/sunrpc/xprtrdma/rpc_rdma.c b/net/sunrpc/xprtrdma/rpc_rdma.c index 3aac1456e23e..5d1f96bffd74 100644 --- a/net/sunrpc/xprtrdma/rpc_rdma.c +++ b/net/sunrpc/xprtrdma/rpc_rdma.c @@ -516,29 +516,11 @@ static int rpcrdma_encode_reply_chunk(struct rpcrdma_xprt *r_xprt, return 0; } -static void rpcrdma_sendctx_done(struct kref *kref) -{ - struct rpcrdma_req *req = - container_of(kref, struct rpcrdma_req, rl_kref); - struct rpcrdma_rep *rep = req->rl_reply; - - rpcrdma_complete_rqst(rep); - rep->rr_rxprt->rx_stats.reply_waits_for_send++; -} - -/** - * rpcrdma_sendctx_unmap - DMA-unmap Send buffer - * @sc: sendctx containing SGEs to unmap - * - */ -void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) +static void rpcrdma_sendctx_dma_unmap(struct rpcrdma_sendctx *sc) { struct rpcrdma_regbuf *rb = sc->sc_req->rl_sendbuf; struct ib_sge *sge; - if (!sc->sc_unmap_count) - return; - /* The first two SGEs contain the transport header and * the inline buffer. These are always left mapped so * they can be cheaply re-used. @@ -547,8 +529,29 @@ void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) ++sge, --sc->sc_unmap_count) ib_dma_unmap_page(rdmab_device(rb), sge->addr, sge->length, DMA_TO_DEVICE); +} - kref_put(&sc->sc_req->rl_kref, rpcrdma_sendctx_done); +/** + * rpcrdma_sendctx_unmap - DMA-unmap Send buffer and release Send owner + * @sc: sendctx containing SGEs to unmap + * + */ +void rpcrdma_sendctx_unmap(struct rpcrdma_sendctx *sc) +{ + struct rpcrdma_req *req = sc->sc_req; + + rpcrdma_sendctx_dma_unmap(sc); + sc->sc_req = NULL; + rpcrdma_req_put(req); +} + +/* No Send was posted. Release DMA mappings prepared for this + * sendctx, but leave the request reference count alone. + */ +static void rpcrdma_sendctx_cancel(struct rpcrdma_sendctx *sc) +{ + rpcrdma_sendctx_dma_unmap(sc); + sc->sc_req = NULL; } /* Prepare an SGE for the RPC-over-RDMA transport header. @@ -740,8 +743,6 @@ static bool rpcrdma_prepare_noch_mapped(struct rpcrdma_xprt *r_xprt, tail->iov_len)) return false; - if (req->rl_sendctx->sc_unmap_count) - kref_get(&req->rl_kref); return true; } @@ -771,7 +772,6 @@ static bool rpcrdma_prepare_readch(struct rpcrdma_xprt *r_xprt, len -= len & 3; if (!rpcrdma_prepare_tail_iov(req, xdr, page_base, len)) return false; - kref_get(&req->rl_kref); } return true; @@ -792,6 +792,7 @@ inline int rpcrdma_prepare_send_sges(struct rpcrdma_xprt *r_xprt, struct xdr_buf *xdr, enum rpcrdma_chunktype rtype) { + struct rpcrdma_sendctx *sc; int ret; ret = -EAGAIN; @@ -800,7 +801,6 @@ inline int rpcrdma_prepare_send_sges(struct rpcrdma_xprt *r_xprt, goto out_nosc; req->rl_sendctx->sc_unmap_count = 0; req->rl_sendctx->sc_req = req; - kref_init(&req->rl_kref); req->rl_wr.wr_cqe = &req->rl_sendctx->sc_cqe; req->rl_wr.sg_list = req->rl_sendctx->sc_sges; req->rl_wr.num_sge = 0; @@ -828,10 +828,16 @@ inline int rpcrdma_prepare_send_sges(struct rpcrdma_xprt *r_xprt, goto out_unmap; } + /* The Send-side owner releases this reference when the + * Send has completed. + */ + kref_get(&req->rl_kref); return 0; out_unmap: - rpcrdma_sendctx_unmap(req->rl_sendctx); + sc = req->rl_sendctx; + rpcrdma_sendctx_cancel(sc); + rpcrdma_sendctx_unget_locked(r_xprt, sc); out_nosc: trace_xprtrdma_prepsend_failed(&req->rl_slot, ret); return ret; @@ -1130,6 +1136,8 @@ rpcrdma_is_bcall(struct rpcrdma_xprt *r_xprt, struct rpcrdma_rep *rep) /* Peek at stream contents without advancing. */ p = xdr_inline_decode(xdr, 0); + if ((char *)xdr->end - (char *)p < 5 * XDR_UNIT) + return false; /* Chunk lists */ if (xdr_item_is_present(p++)) @@ -1154,7 +1162,7 @@ rpcrdma_is_bcall(struct rpcrdma_xprt *r_xprt, struct rpcrdma_rep *rep) */ p = xdr_inline_decode(xdr, 3 * sizeof(*p)); if (unlikely(!p)) - return true; + return false; rpcrdma_bc_receive_call(r_xprt, rep); return true; @@ -1378,6 +1386,11 @@ void rpcrdma_complete_rqst(struct rpcrdma_rep *rep) struct rpc_rqst *rqst = rep->rr_rqst; int status; + /* I3: every registered MR has been invalidated and + * ib_dma_unmap_sg()'d before complete_rqst runs. + */ + WARN_ON_ONCE(!list_empty(&rpcr_to_rdmar(rqst)->rl_registered)); + switch (rep->rr_proc) { case rdma_msg: status = rpcrdma_decode_msg(r_xprt, rep, rqst); @@ -1409,13 +1422,69 @@ out_badheader: goto out; } -static void rpcrdma_reply_done(struct kref *kref) -{ - struct rpcrdma_req *req = - container_of(kref, struct rpcrdma_req, rl_kref); - - rpcrdma_complete_rqst(req->rl_reply); -} +/* Reply-side ownership invariants + * + * I1 (Receive WR ownership). A struct rpcrdma_rep is owned by the + * HCA between ib_post_recv() and the matching Receive completion. + * After ib_dma_sync_single_for_cpu() in rpcrdma_wc_receive() it is + * owned by the CPU until rpcrdma_rep_put() returns it to + * rb_free_reps; a rep on rb_free_reps is not re-posted until + * rpcrdma_post_recvs() pulls it off. Asserted: rpcrdma_post_recvs() + * WARNs that a pulled rep has rr_rqst == NULL. + * + * I2 (rep attachment). While req->rl_reply == rep, the rep cannot be + * re-posted. rpcrdma_reply_put() NULLs req->rl_reply before handing + * the rep to rpcrdma_rep_put(). Asserted: rpcrdma_reply_put() WARNs + * that rl_reply is NULL after the put. + * + * I3 (Registered-MR fence). On entry to rpcrdma_complete_rqst() every + * MR that was on req->rl_registered has had its rkey invalidated + * (remotely via IB_WC_WITH_INVALIDATE or locally via IB_WR_LOCAL_INV) + * and its pages ib_dma_unmap_sg()'d. The LocalInv chain is posted + * on a single QP; strong send-queue ordering makes the last + * completion (frwr_wc_localinv_done) observe the + * ib_dma_unmap_sg() that ran from each earlier completion's + * frwr_mr_put() before complete_rqst is called. The inline + * frwr_reminv() path unmaps its one MR synchronously before + * rpcrdma_reply_handler() reaches complete_rqst. Asserted: + * rpcrdma_complete_rqst() WARNs that rl_registered is empty. + * + * I4 (Send-buffer release). req->rl_kref carries two unconditional + * owners while a Send is outstanding: the RPC-layer reference (set + * at xprt_rdma_alloc_slot / xprt_rdma_bc_rqst_get / rpcrdma_req_release + * pool-entry) and the Send-side reference (kref_get() in + * rpcrdma_prepare_send_sges()). rpcrdma_req_release() runs only + * after both have dropped, so the req does not return to its free + * pool until rpcrdma_sendctx_unmap() has fired -- the HCA has + * released the send buffer before the req can be reused. Asserted: + * rpcrdma_req_release() WARNs that rl_sendctx is NULL. + * + * I5 (req lifecycle). A req is owned by the RPC layer between slot + * acquisition and the matching xprt_rdma_free_slot() (or, for the + * backchannel, xprt_rdma_bc_free_rqst()). While owned, rl_kref >= 1. + * The pools (rb_send_bufs, bc_pa_list, backlog wake target) never + * contain a req with outstanding Send-side or Reply-side work. + * + * Non-hazards. The following claims have been raised by adversarial + * review and are each closed by the invariants above: + * + * * "Reply completes the RPC while the HCA still holds the send + * buffer" -- excluded by I4. The Send-side kref reference is held + * until rpcrdma_sendctx_unmap() runs from Send completion. + * + * * "Signal-driven release races the in-flight Send" -- same + * resolution. xprt_rdma_free() does not touch rl_kref; the + * Send-side reference keeps the req out of its pool until Send + * completion fires. + * + * * "Receive completion races rep reuse" -- excluded by I1. A rep + * is on rb_free_reps only after rpcrdma_rep_put() has been called + * and rpcrdma_post_recvs() owns the next transition back to the HCA. + * + * * "Pages still DMA-mapped when call_decode reads them" -- excluded + * by I3. The matching ib_dma_unmap_sg() for every MR has run on + * the same CPU thread that calls rpcrdma_complete_rqst(). + */ /** * rpcrdma_reply_handler - Process received RPC/RDMA messages @@ -1451,6 +1520,14 @@ void rpcrdma_reply_handler(struct rpcrdma_rep *rep) credits = be32_to_cpu(*p++); rep->rr_proc = *p++; + /* The credit grant from the wire is not trustworthy; + * sanitize it before any code path consumes it. + */ + if (credits == 0) + credits = 1; /* don't deadlock */ + else if (credits > r_xprt->rx_ep->re_max_requests) + credits = r_xprt->rx_ep->re_max_requests; + if (rep->rr_vers != rpcrdma_version) goto out_badversion; @@ -1467,11 +1544,6 @@ void rpcrdma_reply_handler(struct rpcrdma_rep *rep) xprt_pin_rqst(rqst); spin_unlock(&xprt->queue_lock); - if (credits == 0) - credits = 1; /* don't deadlock */ - else if (credits > r_xprt->rx_ep->re_max_requests) - credits = r_xprt->rx_ep->re_max_requests; - rpcrdma_post_recvs(r_xprt, credits + (buf->rb_bc_srv_max_requests << 1)); if (buf->rb_credits != credits) rpcrdma_update_cwnd(r_xprt, credits); @@ -1489,21 +1561,28 @@ void rpcrdma_reply_handler(struct rpcrdma_rep *rep) frwr_unmap_async(r_xprt, req); /* LocalInv completion will complete the RPC */ else - kref_put(&req->rl_kref, rpcrdma_reply_done); - return; + rpcrdma_complete_rqst(rep); -out_badversion: - trace_xprtrdma_reply_vers_err(rep); - goto out; +out_post: + rpcrdma_post_recvs(r_xprt, + credits + (buf->rb_bc_srv_max_requests << 1)); + return; out_norqst: spin_unlock(&xprt->queue_lock); trace_xprtrdma_reply_rqst_err(rep); - goto out; + rpcrdma_rep_put(buf, rep); + goto out_post; + +out_badversion: + trace_xprtrdma_reply_vers_err(rep); + rpcrdma_rep_put(buf, rep); + credits = buf->rb_credits; + goto out_post; out_shortreply: trace_xprtrdma_reply_short_err(rep); - -out: rpcrdma_rep_put(buf, rep); + credits = buf->rb_credits; + goto out_post; } diff --git a/net/sunrpc/xprtrdma/transport.c b/net/sunrpc/xprtrdma/transport.c index 9a8ce5df83ca..5ff8e5126a6c 100644 --- a/net/sunrpc/xprtrdma/transport.c +++ b/net/sunrpc/xprtrdma/transport.c @@ -279,6 +279,13 @@ xprt_rdma_destroy(struct rpc_xprt *xprt) cancel_delayed_work_sync(&r_xprt->rx_connect_worker); rpcrdma_xprt_disconnect(r_xprt); + + /* The disconnect's sendctx drain can return bc_prealloc reqs + * to bc_pa_list after xprt_destroy_backchannel() emptied it. + */ +#if defined(CONFIG_SUNRPC_BACKCHANNEL) + xprt_rdma_bc_destroy(xprt, 0); +#endif rpcrdma_buffer_destroy(&r_xprt->rx_buf); xprt_rdma_free_addresses(xprt); @@ -487,6 +494,50 @@ xprt_rdma_connect(struct rpc_xprt *xprt, struct rpc_task *task) queue_delayed_work(system_long_wq, &r_xprt->rx_connect_worker, delay); } +/* rl_kref has two owners while a Send is outstanding: the rpc_rqst + * owner and the sendctx. Replies complete the RPC but do not drop + * either reference. The req returns to its free pool only after + * xprt_rdma_free_slot() or xprt_rdma_bc_free_rqst() has dropped the + * RPC-layer reference and rpcrdma_sendctx_unmap() has dropped the + * Send-side reference. + */ +static void rpcrdma_req_release(struct kref *kref) +{ + struct rpcrdma_req *req = + container_of(kref, struct rpcrdma_req, rl_kref); + struct rpc_rqst *rqst = &req->rl_slot; + struct rpc_xprt *xprt = rqst->rq_xprt; + struct rpcrdma_xprt *r_xprt; + + /* I4: both the RPC-layer and Send-side owners have dropped, + * so rpcrdma_sendctx_unmap() has cleared rl_sendctx. + */ + WARN_ON_ONCE(req->rl_sendctx); + + kref_init(&req->rl_kref); + +#if defined(CONFIG_SUNRPC_BACKCHANNEL) + if (bc_prealloc(rqst)) { + spin_lock(&xprt->bc_pa_lock); + list_add_tail(&rqst->rq_bc_pa_list, &xprt->bc_pa_list); + spin_unlock(&xprt->bc_pa_lock); + return; + } +#endif + + if (xprt_wake_up_backlog(xprt, rqst)) + return; + + r_xprt = rpcx_to_rdmax(xprt); + memset(rqst, 0, sizeof(*rqst)); + rpcrdma_buffer_put(&r_xprt->rx_buf, req); +} + +void rpcrdma_req_put(struct rpcrdma_req *req) +{ + kref_put(&req->rl_kref, rpcrdma_req_release); +} + /** * xprt_rdma_alloc_slot - allocate an rpc_rqst * @xprt: controlling RPC transport @@ -505,13 +556,28 @@ xprt_rdma_alloc_slot(struct rpc_xprt *xprt, struct rpc_task *task) req = rpcrdma_buffer_get(&r_xprt->rx_buf); if (!req) goto out_sleep; + kref_init(&req->rl_kref); task->tk_rqstp = &req->rl_slot; task->tk_status = 0; return; out_sleep: - task->tk_status = -ENOMEM; - xprt_add_backlog(xprt, task); + task->tk_status = -EAGAIN; + xprt_add_backlog_noncongested(xprt, task); + /* A buffer freed between buffer_get and rpc_sleep_on + * goes back to the pool with no waiter to wake. + * Re-check after joining the backlog to close that gap. + */ + req = rpcrdma_buffer_get(&r_xprt->rx_buf); + if (req) { + struct rpc_rqst *rqst = &req->rl_slot; + + kref_init(&req->rl_kref); + if (!xprt_wake_up_backlog(xprt, rqst)) { + memset(rqst, 0, sizeof(*rqst)); + rpcrdma_buffer_put(&r_xprt->rx_buf, req); + } + } } /** @@ -527,10 +593,7 @@ xprt_rdma_free_slot(struct rpc_xprt *xprt, struct rpc_rqst *rqst) container_of(xprt, struct rpcrdma_xprt, rx_xprt); rpcrdma_reply_put(&r_xprt->rx_buf, rpcr_to_rdmar(rqst)); - if (!xprt_wake_up_backlog(xprt, rqst)) { - memset(rqst, 0, sizeof(*rqst)); - rpcrdma_buffer_put(&r_xprt->rx_buf, rpcr_to_rdmar(rqst)); - } + rpcrdma_req_put(rpcr_to_rdmar(rqst)); } static bool rpcrdma_check_regbuf(struct rpcrdma_xprt *r_xprt, @@ -594,10 +657,10 @@ xprt_rdma_free(struct rpc_task *task) frwr_unmap_sync(rpcx_to_rdmax(rqst->rq_xprt), req); } - /* XXX: If the RPC is completing because of a signal and - * not because a reply was received, we ought to ensure - * that the Send completion has fired, so that memory - * involved with the Send is not still visible to the NIC. + /* The Send-side rl_kref owner keeps req out of its free pool + * until rpcrdma_sendctx_unmap() has fired -- see I4 above + * rpcrdma_reply_handler() -- so signal-driven release here + * does not let the HCA touch a recycled send buffer. */ } @@ -703,7 +766,7 @@ void xprt_rdma_print_stats(struct rpc_xprt *xprt, struct seq_file *seq) r_xprt->rx_stats.mrs_allocated, r_xprt->rx_stats.local_inv_needed, r_xprt->rx_stats.empty_sendctx_q, - r_xprt->rx_stats.reply_waits_for_send); + 0LU); /* was reply_waits_for_send; column preserved */ } static int diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c index 8abbd9c4045a..efd91ed249fa 100644 --- a/net/sunrpc/xprtrdma/verbs.c +++ b/net/sunrpc/xprtrdma/verbs.c @@ -65,6 +65,8 @@ static int rpcrdma_sendctxs_create(struct rpcrdma_xprt *r_xprt); static void rpcrdma_sendctxs_destroy(struct rpcrdma_xprt *r_xprt); +static unsigned long rpcrdma_sendctx_next(struct rpcrdma_buffer *buf, + unsigned long item); static void rpcrdma_sendctx_put_locked(struct rpcrdma_xprt *r_xprt, struct rpcrdma_sendctx *sc); static int rpcrdma_reqs_setup(struct rpcrdma_xprt *r_xprt); @@ -79,6 +81,8 @@ rpcrdma_regbuf_alloc_node(size_t size, enum dma_data_direction direction, int node); static struct rpcrdma_regbuf * rpcrdma_regbuf_alloc(size_t size, enum dma_data_direction direction); +static bool rpcrdma_regbuf_realloc_node(struct rpcrdma_regbuf *rb, + size_t size, gfp_t flags, int node); static void rpcrdma_regbuf_dma_unmap(struct rpcrdma_regbuf *rb); static void rpcrdma_regbuf_free(struct rpcrdma_regbuf *rb); @@ -243,8 +247,17 @@ rpcrdma_cm_event_handler(struct rdma_cm_id *id, struct rdma_cm_event *event) complete(&ep->re_done); return 0; case RDMA_CM_EVENT_ADDR_CHANGE: - ep->re_connect_status = -ENODEV; - goto disconnected; + switch (xchg(&ep->re_connect_status, -ENODEV)) { + case 0: + goto wake_connect_worker; + case 1: + /* The later DISCONNECTED event balances the + * ESTABLISHED get; do not put here. + */ + rpcrdma_force_disconnect(ep); + return 0; + } + return 0; case RDMA_CM_EVENT_ESTABLISHED: rpcrdma_ep_get(ep); ep->re_connect_status = 1; @@ -267,7 +280,6 @@ wake_connect_worker: return 0; case RDMA_CM_EVENT_DISCONNECTED: ep->re_connect_status = -ECONNABORTED; -disconnected: rpcrdma_force_disconnect(ep); return rpcrdma_ep_put(ep); default: @@ -324,6 +336,7 @@ static struct rdma_cm_id *rpcrdma_create_id(struct rpcrdma_xprt *r_xprt, if (rc) goto out; + ep->re_id = id; rc = rpcrdma_rn_register(id->device, &ep->re_rn, rpcrdma_ep_removal_done); if (rc) goto out; @@ -396,7 +409,6 @@ static int rpcrdma_ep_create(struct rpcrdma_xprt *r_xprt) } __module_get(THIS_MODULE); device = id->device; - ep->re_id = id; reinit_completion(&ep->re_done); ep->re_max_requests = r_xprt->rx_xprt.max_reqs; @@ -539,7 +551,17 @@ int rpcrdma_xprt_connect(struct rpcrdma_xprt *r_xprt) goto out; } rpcrdma_mrs_create(r_xprt); - frwr_wp_create(r_xprt); + + /* + * rpcrdma_encode_write_list() dereferences the write-pad + * MR with no NULL check, so fail the connect rather than + * publish a transport whose write-pad MR is NULL. + */ + rc = frwr_wp_create(r_xprt); + if (rc) { + rc = -ENOTCONN; + goto out; + } out: trace_xprtrdma_connect(r_xprt, rc); @@ -571,9 +593,9 @@ void rpcrdma_xprt_disconnect(struct rpcrdma_xprt *r_xprt) rpcrdma_xprt_drain(r_xprt); rpcrdma_reps_unmap(r_xprt); + rpcrdma_sendctxs_destroy(r_xprt); rpcrdma_reqs_reset(r_xprt); rpcrdma_mrs_destroy(r_xprt); - rpcrdma_sendctxs_destroy(r_xprt); if (rpcrdma_ep_put(ep)) rdma_destroy_id(id); @@ -605,6 +627,25 @@ static void rpcrdma_sendctxs_destroy(struct rpcrdma_xprt *r_xprt) if (!buf->rb_sc_ctxs) return; + + /* The QP is drained, but the final unsignaled Sends might not + * have been walked by a signaled Send completion. Release those + * Send owners before request buffers are reset. + * + * Unlike the completion sweep, this walk can visit slots with + * no Send posted: after a partial rpcrdma_sendctxs_create() + * failure on reconnect, rb_sc_head and rb_sc_tail are stale, + * and slots between them can be NULL or have sc_req clear. + */ + for (i = rpcrdma_sendctx_next(buf, buf->rb_sc_tail); + i != rpcrdma_sendctx_next(buf, buf->rb_sc_head); + i = rpcrdma_sendctx_next(buf, i)) { + struct rpcrdma_sendctx *sc = buf->rb_sc_ctxs[i]; + + if (sc && sc->sc_req) + rpcrdma_sendctx_unmap(sc); + } + for (i = 0; i <= buf->rb_sc_last; i++) kfree(buf->rb_sc_ctxs[i]); kfree(buf->rb_sc_ctxs); @@ -667,6 +708,12 @@ static unsigned long rpcrdma_sendctx_next(struct rpcrdma_buffer *buf, return likely(item < buf->rb_sc_last) ? item + 1 : 0; } +static unsigned long rpcrdma_sendctx_prev(struct rpcrdma_buffer *buf, + unsigned long item) +{ + return item > 0 ? item - 1 : buf->rb_sc_last; +} + /** * rpcrdma_sendctx_get_locked - Acquire a send context * @r_xprt: controlling transport instance @@ -712,6 +759,29 @@ out_emptyq: } /** + * rpcrdma_sendctx_unget_locked - Release an unposted send context + * @r_xprt: controlling transport instance + * @sc: send context to release + * + * Usage: Called when no Send is posted for the sendctx most + * recently returned by rpcrdma_sendctx_get_locked(). + * + * The caller serializes calls to this function and to + * rpcrdma_sendctx_get_locked() (per transport). + */ +void rpcrdma_sendctx_unget_locked(struct rpcrdma_xprt *r_xprt, + struct rpcrdma_sendctx *sc) +{ + struct rpcrdma_buffer *buf = &r_xprt->rx_buf; + + if (WARN_ON_ONCE(buf->rb_sc_ctxs[buf->rb_sc_head] != sc)) + return; + + buf->rb_sc_head = rpcrdma_sendctx_prev(buf, buf->rb_sc_head); + xprt_write_space(&r_xprt->rx_xprt); +} + +/** * rpcrdma_sendctx_put_locked - Release a send context * @r_xprt: controlling transport instance * @sc: send context to release @@ -727,15 +797,18 @@ static void rpcrdma_sendctx_put_locked(struct rpcrdma_xprt *r_xprt, struct rpcrdma_buffer *buf = &r_xprt->rx_buf; unsigned long next_tail; - /* Unmap SGEs of previously completed but unsignaled - * Sends by walking up the queue until @sc is found. + /* Release previously completed but unsignaled Sends by walking + * up the queue until @sc is found. */ next_tail = buf->rb_sc_tail; do { + struct rpcrdma_sendctx *cur; + next_tail = rpcrdma_sendctx_next(buf, next_tail); /* ORDER: item must be accessed _before_ tail is updated */ - rpcrdma_sendctx_unmap(buf->rb_sc_ctxs[next_tail]); + cur = buf->rb_sc_ctxs[next_tail]; + rpcrdma_sendctx_unmap(cur); } while (buf->rb_sc_ctxs[next_tail] != sc); @@ -1196,6 +1269,10 @@ void rpcrdma_reply_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req) rpcrdma_rep_put(buffers, req->rl_reply); req->rl_reply = NULL; } + /* I2: rl_reply NULL after the put closes the + * 'rep on rb_free_reps still referenced by req' window. + */ + WARN_ON_ONCE(req->rl_reply); } /** @@ -1277,9 +1354,15 @@ rpcrdma_regbuf_alloc(size_t size, enum dma_data_direction direction) */ bool rpcrdma_regbuf_realloc(struct rpcrdma_regbuf *rb, size_t size, gfp_t flags) { + return rpcrdma_regbuf_realloc_node(rb, size, flags, NUMA_NO_NODE); +} + +static bool rpcrdma_regbuf_realloc_node(struct rpcrdma_regbuf *rb, + size_t size, gfp_t flags, int node) +{ void *buf; - buf = kmalloc(size, flags); + buf = kmalloc_node(size, flags, node); if (!buf) return false; @@ -1291,6 +1374,23 @@ bool rpcrdma_regbuf_realloc(struct rpcrdma_regbuf *rb, size_t size, gfp_t flags) return true; } +static bool rpcrdma_rep_resize(struct rpcrdma_xprt *r_xprt, + struct rpcrdma_rep *rep) +{ + struct rpcrdma_regbuf *rb = rep->rr_rdmabuf; + struct rpcrdma_ep *ep = r_xprt->rx_ep; + size_t size = ep->re_inline_recv; + + if (likely(rdmab_length(rb) >= size)) + return true; + if (!rpcrdma_regbuf_realloc_node(rb, size, XPRTRDMA_GFP_FLAGS, + ibdev_to_node(ep->re_id->device))) + return false; + + xdr_buf_init(&rep->rr_hdrbuf, rdmab_data(rb), rdmab_length(rb)); + return true; +} + /** * __rpcrdma_regbuf_dma_map - DMA-map a regbuf * @r_xprt: controlling transport instance @@ -1372,6 +1472,12 @@ void rpcrdma_post_recvs(struct rpcrdma_xprt *r_xprt, int needed) rep = rpcrdma_rep_create(r_xprt); if (!rep) break; + /* I1: a rep on rb_free_reps must carry no rqst pointer. */ + WARN_ON_ONCE(rep->rr_rqst); + if (!rpcrdma_rep_resize(r_xprt, rep)) { + rpcrdma_rep_put(buf, rep); + break; + } if (!rpcrdma_regbuf_dma_map(r_xprt, rep->rr_rdmabuf)) { rpcrdma_rep_put(buf, rep); break; diff --git a/net/sunrpc/xprtrdma/xprt_rdma.h b/net/sunrpc/xprtrdma/xprt_rdma.h index 8147d2b41494..ae14f6a526e6 100644 --- a/net/sunrpc/xprtrdma/xprt_rdma.h +++ b/net/sunrpc/xprtrdma/xprt_rdma.h @@ -410,7 +410,6 @@ struct rpcrdma_stats { /* accessed when receiving a reply */ unsigned long long total_rdma_reply; unsigned long long fixup_copy_count; - unsigned long reply_waits_for_send; unsigned long local_inv_needed; unsigned long nomsg_call_count; unsigned long bcall_count; @@ -479,6 +478,8 @@ void rpcrdma_req_destroy(struct rpcrdma_req *req); int rpcrdma_buffer_create(struct rpcrdma_xprt *); void rpcrdma_buffer_destroy(struct rpcrdma_buffer *); struct rpcrdma_sendctx *rpcrdma_sendctx_get_locked(struct rpcrdma_xprt *r_xprt); +void rpcrdma_sendctx_unget_locked(struct rpcrdma_xprt *r_xprt, + struct rpcrdma_sendctx *sc); struct rpcrdma_mr *rpcrdma_mr_get(struct rpcrdma_xprt *r_xprt); void rpcrdma_mrs_refresh(struct rpcrdma_xprt *r_xprt); @@ -488,6 +489,7 @@ void rpcrdma_buffer_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req); void rpcrdma_rep_put(struct rpcrdma_buffer *buf, struct rpcrdma_rep *rep); void rpcrdma_reply_put(struct rpcrdma_buffer *buffers, struct rpcrdma_req *req); +void rpcrdma_req_put(struct rpcrdma_req *req); bool rpcrdma_regbuf_realloc(struct rpcrdma_regbuf *rb, size_t size, gfp_t flags); diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c index 3aa987e7f072..6b64c6c49494 100644 --- a/net/sunrpc/xprtsock.c +++ b/net/sunrpc/xprtsock.c @@ -2733,8 +2733,11 @@ static void xs_tcp_tls_setup_socket(struct work_struct *work) lower_xprt = rcu_dereference(lower_clnt->cl_xprt); rcu_read_unlock(); - if (wait_on_bit_lock(&lower_xprt->state, XPRT_LOCKED, TASK_KILLABLE)) + if (wait_on_bit_lock(&lower_xprt->state, XPRT_LOCKED, TASK_KILLABLE)) { + /* XPRT_LOCKED was never acquired. */ + rpc_shutdown_client(lower_clnt); goto out_unlock; + } status = xs_tls_handshake_sync(lower_xprt, &upper_xprt->xprtsec); if (status) { @@ -2757,6 +2760,7 @@ static void xs_tcp_tls_setup_socket(struct work_struct *work) out_unlock: current_restore_flags(pflags, PF_MEMALLOC); upper_transport->clnt = NULL; + rpc_release_client(upper_clnt); xprt_unlock_connect(upper_xprt, upper_transport); return; @@ -2804,7 +2808,15 @@ static void xs_connect(struct rpc_xprt *xprt, struct rpc_task *task) } else dprintk("RPC: xs_connect scheduled xprt %p\n", xprt); - transport->clnt = task->tk_client; + /* + * Only the TLS connect_worker reads transport->clnt; pinning + * the upper rpc_clnt unconditionally would form a cycle with + * cl_xprt and prevent xprt destruction. + */ + if (xprt->xprtsec.policy != RPC_XPRTSEC_NONE) { + rpc_hold_client(task->tk_client); + transport->clnt = task->tk_client; + } queue_delayed_work(xprtiod_workqueue, &transport->connect_worker, delay); diff --git a/net/tipc/bearer.c b/net/tipc/bearer.c index ae1ddbf71853..8866a3d71313 100644 --- a/net/tipc/bearer.c +++ b/net/tipc/bearer.c @@ -482,6 +482,7 @@ void tipc_disable_l2_media(struct tipc_bearer *b) dev = (struct net_device *)rtnl_dereference(b->media_ptr); dev_remove_pack(&b->pt); RCU_INIT_POINTER(dev->tipc_ptr, NULL); + RCU_INIT_POINTER(b->media_ptr, NULL); synchronize_net(); dev_put(dev); } diff --git a/net/tipc/core.c b/net/tipc/core.c index 434e70eabe08..1ddecea1df6e 100644 --- a/net/tipc/core.c +++ b/net/tipc/core.c @@ -218,6 +218,11 @@ static void __exit tipc_exit(void) unregister_pernet_device(&tipc_net_ops); tipc_unregister_sysctl(); + /* TODO: Wait for all timers that called call_rcu() to finish before + * calling rcu_barrier(). + */ + rcu_barrier(); + pr_info("Deactivated\n"); } diff --git a/net/tipc/discover.c b/net/tipc/discover.c index 775fd4f3f072..f13a823c13c6 100644 --- a/net/tipc/discover.c +++ b/net/tipc/discover.c @@ -58,6 +58,7 @@ * @skb: request message to be (repeatedly) sent * @timer: timer governing period between requests * @timer_intv: current interval between requests (in ms) + * @rcu: RCU head for deferred freeing */ struct tipc_discoverer { u32 bearer_id; @@ -69,6 +70,7 @@ struct tipc_discoverer { struct sk_buff *skb; struct timer_list timer; unsigned long timer_intv; + struct rcu_head rcu; }; /** @@ -382,6 +384,15 @@ int tipc_disc_create(struct net *net, struct tipc_bearer *b, return 0; } +static void tipc_disc_free_rcu(struct rcu_head *rp) +{ + struct tipc_discoverer *d = container_of(rp, struct tipc_discoverer, + rcu); + + kfree_skb(d->skb); + kfree(d); +} + /** * tipc_disc_delete - destroy object sending periodic link setup requests * @d: ptr to link dest structure @@ -389,8 +400,7 @@ int tipc_disc_create(struct net *net, struct tipc_bearer *b, void tipc_disc_delete(struct tipc_discoverer *d) { timer_shutdown_sync(&d->timer); - kfree_skb(d->skb); - kfree(d); + call_rcu(&d->rcu, tipc_disc_free_rcu); } /** diff --git a/net/tipc/name_distr.c b/net/tipc/name_distr.c index 190b49c5cbc3..ba4f4906e13b 100644 --- a/net/tipc/name_distr.c +++ b/net/tipc/name_distr.c @@ -280,12 +280,21 @@ static bool tipc_update_nametbl(struct net *net, struct distr_item *i, u32 node, u32 dtype) { struct publication *p = NULL; + u32 lower = ntohl(i->lower); + u32 upper = ntohl(i->upper); struct tipc_socket_addr sk; - struct tipc_uaddr ua; u32 key = ntohl(i->key); + struct tipc_uaddr ua; + + /* A peer-advertised binding with lower > upper can never be matched + * or withdrawn and would leak the publication; the local bind path + * rejects such ranges, so reject ranges learned from the network too. + */ + if (lower > upper) + return false; tipc_uaddr(&ua, TIPC_SERVICE_RANGE, TIPC_CLUSTER_SCOPE, - ntohl(i->type), ntohl(i->lower), ntohl(i->upper)); + ntohl(i->type), lower, upper); sk.ref = ntohl(i->port); sk.node = node; diff --git a/net/tipc/netlink.c b/net/tipc/netlink.c index 1a9a5bdaccf4..8336a9664703 100644 --- a/net/tipc/netlink.c +++ b/net/tipc/netlink.c @@ -152,11 +152,13 @@ static const struct genl_ops tipc_genl_v2_ops[] = { { .cmd = TIPC_NL_BEARER_DISABLE, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .flags = GENL_UNS_ADMIN_PERM, .doit = tipc_nl_bearer_disable, }, { .cmd = TIPC_NL_BEARER_ENABLE, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .flags = GENL_UNS_ADMIN_PERM, .doit = tipc_nl_bearer_enable, }, { @@ -168,11 +170,13 @@ static const struct genl_ops tipc_genl_v2_ops[] = { { .cmd = TIPC_NL_BEARER_ADD, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .flags = GENL_UNS_ADMIN_PERM, .doit = tipc_nl_bearer_add, }, { .cmd = TIPC_NL_BEARER_SET, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .flags = GENL_UNS_ADMIN_PERM, .doit = tipc_nl_bearer_set, }, { @@ -197,11 +201,13 @@ static const struct genl_ops tipc_genl_v2_ops[] = { { .cmd = TIPC_NL_LINK_SET, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .flags = GENL_UNS_ADMIN_PERM, .doit = tipc_nl_node_set_link, }, { .cmd = TIPC_NL_LINK_RESET_STATS, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .flags = GENL_UNS_ADMIN_PERM, .doit = tipc_nl_node_reset_link_stats, }, { @@ -213,6 +219,7 @@ static const struct genl_ops tipc_genl_v2_ops[] = { { .cmd = TIPC_NL_MEDIA_SET, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .flags = GENL_UNS_ADMIN_PERM, .doit = tipc_nl_media_set, }, { @@ -228,6 +235,7 @@ static const struct genl_ops tipc_genl_v2_ops[] = { { .cmd = TIPC_NL_NET_SET, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .flags = GENL_UNS_ADMIN_PERM, .doit = tipc_nl_net_set, }, { @@ -238,6 +246,7 @@ static const struct genl_ops tipc_genl_v2_ops[] = { { .cmd = TIPC_NL_MON_SET, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .flags = GENL_UNS_ADMIN_PERM, .doit = tipc_nl_node_set_monitor, }, { @@ -255,6 +264,7 @@ static const struct genl_ops tipc_genl_v2_ops[] = { { .cmd = TIPC_NL_PEER_REMOVE, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .flags = GENL_UNS_ADMIN_PERM, .doit = tipc_nl_peer_rm, }, #ifdef CONFIG_TIPC_MEDIA_UDP @@ -269,11 +279,13 @@ static const struct genl_ops tipc_genl_v2_ops[] = { { .cmd = TIPC_NL_KEY_SET, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .flags = GENL_UNS_ADMIN_PERM, .doit = tipc_nl_node_set_key, }, { .cmd = TIPC_NL_KEY_FLUSH, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .flags = GENL_UNS_ADMIN_PERM, .doit = tipc_nl_node_flush_key, }, #endif diff --git a/net/tipc/socket.c b/net/tipc/socket.c index 3d5de693a222..677e17ec2939 100644 --- a/net/tipc/socket.c +++ b/net/tipc/socket.c @@ -1362,6 +1362,9 @@ static void tipc_sk_conn_proto_rcv(struct tipc_sock *tsk, struct sk_buff *skb, __skb_queue_tail(xmitq, skb); return; } else if (mtyp == CONN_ACK) { + if (tsk->snt_unacked < msg_conn_ack(hdr)) + goto exit; + was_cong = tsk_conn_cong(tsk); tipc_sk_push_backlog(tsk, msg_nagle_ack(hdr)); tsk->snt_unacked -= msg_conn_ack(hdr); @@ -2452,17 +2455,17 @@ static void tipc_sk_enqueue(struct sk_buff_head *inputq, struct sock *sk, atomic_set(dcnt, 0); lim = rcvbuf_limit(sk, skb) + atomic_read(dcnt); if (likely(!sk_add_backlog(sk, skb, lim))) { - trace_tipc_sk_overlimit1(sk, skb, TIPC_DUMP_ALL, + trace_tipc_sk_overlimit1(sk, skb, TIPC_DUMP_SK_BKLGQ, "bklg & rcvq >90% allocated!"); continue; } - trace_tipc_sk_dump(sk, skb, TIPC_DUMP_ALL, "err_overload!"); + trace_tipc_sk_dump(sk, skb, TIPC_DUMP_SK_BKLGQ, "err_overload!"); /* Overload => reject message back to sender */ onode = tipc_own_addr(sock_net(sk)); sk_drops_inc(sk); if (tipc_msg_reverse(onode, &skb, TIPC_ERR_OVERLOAD)) { - trace_tipc_sk_rej_msg(sk, skb, TIPC_DUMP_ALL, + trace_tipc_sk_rej_msg(sk, skb, TIPC_DUMP_SK_BKLGQ, "@sk_enqueue!"); __skb_queue_tail(xmitq, skb); } diff --git a/net/tls/tls.h b/net/tls/tls.h index a1d8467bece3..12f44cb649c9 100644 --- a/net/tls/tls.h +++ b/net/tls/tls.h @@ -161,7 +161,7 @@ void tls_sw_free_resources_rx(struct sock *sk); void tls_sw_release_resources_rx(struct sock *sk); void tls_sw_free_ctx_rx(struct tls_context *tls_ctx); int tls_sw_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len); + int flags); bool tls_sw_sock_is_readable(struct sock *sk); ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos, struct pipe_inode_info *pipe, diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c index 9949ae027081..f19b504efa5d 100644 --- a/net/tls/tls_sw.c +++ b/net/tls/tls_sw.c @@ -2064,8 +2064,7 @@ static void tls_rx_reader_unlock(struct sock *sk, struct tls_sw_context_rx *ctx) int tls_sw_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, - int *addr_len) + int flags) { struct tls_context *tls_ctx = tls_get_ctx(sk); struct tls_sw_context_rx *ctx = tls_sw_ctx_rx(tls_ctx); @@ -2425,6 +2424,17 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc, goto read_sock_requeue; } + /* An empty data record (legal in TLS 1.3) gives a zero + * read_actor return, indistinguishable from the consumer + * stalling; the used <= 0 path would requeue it at the + * head of rx_list and block all later records. Consume it + * here instead. + */ + if (rxm->full_len == 0) { + consume_skb(skb); + continue; + } + used = read_actor(desc, skb, rxm->offset, rxm->full_len); if (used <= 0) { if (!copied) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index b339f83caf03..b65479a738f4 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -1985,16 +1985,19 @@ static void unix_peek_fds(struct scm_cookie *scm, struct sk_buff *skb) static void unix_destruct_scm(struct sk_buff *skb) { - struct scm_cookie scm; + struct scm_cookie scm = {}; + + swap(scm.pid, UNIXCB(skb).pid); - memset(&scm, 0, sizeof(scm)); - scm.pid = UNIXCB(skb).pid; if (UNIXCB(skb).fp) unix_detach_fds(&scm, skb); - /* Alas, it calls VFS */ - /* So fscking what? fput() had been SMP-safe since the last Summer */ scm_destroy(&scm); +} + +static void unix_wfree(struct sk_buff *skb) +{ + unix_destruct_scm(skb); sock_wfree(skb); } @@ -2010,7 +2013,7 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen if (scm->fp && send_fds) err = unix_attach_fds(scm, skb); - skb->destructor = unix_destruct_scm; + skb->destructor = unix_wfree; return err; } @@ -2087,6 +2090,13 @@ static void scm_stat_del(struct sock *sk, struct sk_buff *skb) } } +static void unix_orphan_scm(struct sock *sk, struct sk_buff *skb) +{ + scm_stat_del(sk, skb); + unix_destruct_scm(skb); + skb->destructor = sock_wfree; +} + /* * Send AF_UNIX data. */ @@ -2692,7 +2702,7 @@ static int unix_dgram_recvmsg(struct socket *sock, struct msghdr *msg, size_t si const struct proto *prot = READ_ONCE(sk->sk_prot); if (prot != &unix_dgram_proto) - return prot->recvmsg(sk, msg, size, flags, NULL); + return prot->recvmsg(sk, msg, size, flags); #endif return __unix_dgram_recvmsg(sk, msg, size, flags); } @@ -2704,10 +2714,16 @@ static int unix_read_skb(struct sock *sk, skb_read_actor_t recv_actor) int err; mutex_lock(&u->iolock); + skb = skb_recv_datagram(sk, MSG_DONTWAIT, &err); - mutex_unlock(&u->iolock); - if (!skb) + if (!skb) { + mutex_unlock(&u->iolock); return err; + } + + unix_orphan_scm(sk, skb); + + mutex_unlock(&u->iolock); return recv_actor(sk, skb); } @@ -2905,6 +2921,9 @@ static int unix_stream_read_skb(struct sock *sk, skb_read_actor_t recv_actor) #endif spin_unlock(&queue->lock); + + unix_orphan_scm(sk, skb); + mutex_unlock(&u->iolock); return recv_actor(sk, skb); @@ -3160,7 +3179,7 @@ static int unix_stream_recvmsg(struct socket *sock, struct msghdr *msg, const struct proto *prot = READ_ONCE(sk->sk_prot); if (prot != &unix_stream_proto) - return prot->recvmsg(sk, msg, size, flags, NULL); + return prot->recvmsg(sk, msg, size, flags); #endif return unix_stream_read_generic(&state, true); } diff --git a/net/unix/unix_bpf.c b/net/unix/unix_bpf.c index 57f3124c9d8d..f86ff19e9764 100644 --- a/net/unix/unix_bpf.c +++ b/net/unix/unix_bpf.c @@ -49,7 +49,7 @@ static int __unix_recvmsg(struct sock *sk, struct msghdr *msg, } static int unix_bpf_recvmsg(struct sock *sk, struct msghdr *msg, - size_t len, int flags, int *addr_len) + size_t len, int flags) { struct unix_sock *u = unix_sk(sk); struct sk_psock *psock; diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 2db48b53e47c..d007ce89d19b 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -1412,7 +1412,7 @@ int vsock_dgram_recvmsg(struct socket *sock, struct msghdr *msg, prot = READ_ONCE(sk->sk_prot); if (prot != &vsock_proto) - return prot->recvmsg(sk, msg, len, flags, NULL); + return prot->recvmsg(sk, msg, len, flags); #endif return __vsock_dgram_recvmsg(sock, msg, len, flags); @@ -2485,7 +2485,7 @@ vsock_connectible_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, prot = READ_ONCE(sk->sk_prot); if (prot != &vsock_proto) - return prot->recvmsg(sk, msg, len, flags, NULL); + return prot->recvmsg(sk, msg, len, flags); #endif return __vsock_connectible_recvmsg(sock, msg, len, flags); diff --git a/net/vmw_vsock/vsock_bpf.c b/net/vmw_vsock/vsock_bpf.c index 07b96d56f3a5..9049d2648646 100644 --- a/net/vmw_vsock/vsock_bpf.c +++ b/net/vmw_vsock/vsock_bpf.c @@ -74,7 +74,7 @@ static int __vsock_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int } static int vsock_bpf_recvmsg(struct sock *sk, struct msghdr *msg, - size_t len, int flags, int *addr_len) + size_t len, int flags) { struct sk_psock *psock; struct vsock_sock *vsk; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 54a4585eb3a2..6e41a4269e1c 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -4828,7 +4828,7 @@ static int nl80211_validate_key_link_id(struct genl_info *info, if (wdev->valid_links) { if (link_id == -1) { GENL_SET_ERR_MSG(info, - "link ID must for MLO group key"); + "link ID must be set for MLO group key"); return -EINVAL; } if (!(wdev->valid_links & BIT(link_id))) { diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 199c63de0145..2f2104bb0d52 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -326,8 +326,11 @@ cfg80211_gen_new_ie(const u8 *ie, size_t ielen, /* For ML probe response, match the MLE in the frame body with * MLD id being 'bssid_index' */ - if (parent->id == WLAN_EID_EXTENSION && parent->datalen > 1 && + if (parent->id == WLAN_EID_EXTENSION && parent->data[0] == WLAN_EID_EXT_EHT_MULTI_LINK && + ieee80211_mle_type_ok(parent->data + 1, + IEEE80211_ML_CONTROL_TYPE_BASIC, + parent->datalen - 1) && bssid_index == ieee80211_mle_get_mld_id(parent->data + 1)) { if (!cfg80211_copy_elem_with_frags(parent, ie, ielen, diff --git a/net/xfrm/espintcp.c b/net/xfrm/espintcp.c index b951700dffc4..dd7a7dc02785 100644 --- a/net/xfrm/espintcp.c +++ b/net/xfrm/espintcp.c @@ -133,7 +133,7 @@ static int espintcp_parse(struct strparser *strp, struct sk_buff *skb) } static int espintcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags, int *addr_len) + int flags) { struct espintcp_ctx *ctx = espintcp_getctx(sk); struct sk_buff *skb; @@ -215,43 +215,23 @@ static int espintcp_sendskmsg_locked(struct sock *sk, struct sk_msg *skmsg = &emsg->skmsg; bool more = flags & MSG_MORE; struct scatterlist *sg; - int done = 0; int ret; - sg = &skmsg->sg.data[skmsg->sg.start]; do { struct bio_vec bvec; - size_t size = sg->length - emsg->offset; - int offset = sg->offset + emsg->offset; - struct page *p; - - emsg->offset = 0; + sg = &skmsg->sg.data[skmsg->sg.start]; if (sg_is_last(sg) && !more) msghdr.msg_flags &= ~MSG_MORE; - p = sg_page(sg); -retry: - bvec_set_page(&bvec, p, size, offset); - iov_iter_bvec(&msghdr.msg_iter, ITER_SOURCE, &bvec, 1, size); - ret = tcp_sendmsg_locked(sk, &msghdr, size); - if (ret < 0) { - emsg->offset = offset - sg->offset; - skmsg->sg.start += done; + bvec_set_page(&bvec, sg_page(sg), sg->length, sg->offset); + iov_iter_bvec(&msghdr.msg_iter, ITER_SOURCE, &bvec, 1, sg->length); + ret = tcp_sendmsg_locked(sk, &msghdr, sg->length); + if (ret < 0) return ret; - } - - if (ret != size) { - offset += ret; - size -= ret; - goto retry; - } - done++; - put_page(p); - sk_mem_uncharge(sk, sg->length); - sg = sg_next(sg); - } while (sg); + sk_msg_free_partial(sk, skmsg, ret); + } while (skmsg->sg.size); memset(emsg, 0, sizeof(*emsg)); diff --git a/net/xfrm/xfrm_interface_core.c b/net/xfrm/xfrm_interface_core.c index 330a05286a56..688306bf62c5 100644 --- a/net/xfrm/xfrm_interface_core.c +++ b/net/xfrm/xfrm_interface_core.c @@ -869,6 +869,9 @@ static int xfrmi_changelink(struct net_device *dev, struct nlattr *tb[], struct net *net = xi->net; struct xfrm_if_parms p = {}; + if (!rtnl_dev_link_net_capable(dev, net)) + return -EPERM; + xfrmi_netlink_parms(data, &p); if (!p.if_id) { NL_SET_ERR_MSG(extack, "if_id must be non zero"); diff --git a/net/xfrm/xfrm_nat_keepalive.c b/net/xfrm/xfrm_nat_keepalive.c index 1856beee0149..f50b1f48f2ed 100644 --- a/net/xfrm/xfrm_nat_keepalive.c +++ b/net/xfrm/xfrm_nat_keepalive.c @@ -55,8 +55,10 @@ static int nat_keepalive_send_ipv4(struct sk_buff *skb, ka->encap_sport, sock_net_uid(net, NULL)); rt = ip_route_output_key(net, &fl4); - if (IS_ERR(rt)) + if (IS_ERR(rt)) { + kfree_skb(skb); return PTR_ERR(rt); + } skb_dst_set(skb, &rt->dst); @@ -101,6 +103,7 @@ static int nat_keepalive_send_ipv6(struct sk_buff *skb, dst = ipv6_stub->ipv6_dst_lookup_flow(net, sk, &fl6, NULL); if (IS_ERR(dst)) { local_unlock_nested_bh(&nat_keepalive_sk_ipv6.bh_lock); + kfree_skb(skb); return PTR_ERR(dst); } @@ -118,7 +121,6 @@ static void nat_keepalive_send(struct nat_keepalive *ka) sizeof(struct ipv6hdr)) + sizeof(struct udphdr); const u8 nat_ka_payload = 0xFF; - int err = -EAFNOSUPPORT; struct sk_buff *skb; struct udphdr *uh; @@ -140,16 +142,17 @@ static void nat_keepalive_send(struct nat_keepalive *ka) switch (ka->family) { case AF_INET: - err = nat_keepalive_send_ipv4(skb, ka); + nat_keepalive_send_ipv4(skb, ka); break; #if IS_ENABLED(CONFIG_IPV6) case AF_INET6: - err = nat_keepalive_send_ipv6(skb, ka, uh); + nat_keepalive_send_ipv6(skb, ka, uh); break; #endif - } - if (err) + default: kfree_skb(skb); + break; + } } struct nat_keepalive_work_ctx { diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c index c76625d511ec..4dad1f817228 100644 --- a/net/xfrm/xfrm_policy.c +++ b/net/xfrm/xfrm_policy.c @@ -242,6 +242,9 @@ __xfrm6_selector_match(const struct xfrm_selector *sel, const struct flowi *fl) bool xfrm_selector_match(const struct xfrm_selector *sel, const struct flowi *fl, unsigned short family) { + if (family != sel->family && sel->family != AF_UNSPEC) + return false; + switch (family) { case AF_INET: return __xfrm4_selector_match(sel, fl); @@ -685,7 +688,7 @@ static void xfrm_byidx_resize(struct net *net) static inline int xfrm_bydst_should_resize(struct net *net, int dir, int *total) { - unsigned int cnt = net->xfrm.policy_count[dir]; + unsigned int cnt = READ_ONCE(net->xfrm.policy_count[dir]); unsigned int hmask = net->xfrm.policy_bydst[dir].hmask; if (total) @@ -711,12 +714,12 @@ static inline int xfrm_byidx_should_resize(struct net *net, int total) void xfrm_spd_getinfo(struct net *net, struct xfrmk_spdinfo *si) { - si->incnt = net->xfrm.policy_count[XFRM_POLICY_IN]; - si->outcnt = net->xfrm.policy_count[XFRM_POLICY_OUT]; - si->fwdcnt = net->xfrm.policy_count[XFRM_POLICY_FWD]; - si->inscnt = net->xfrm.policy_count[XFRM_POLICY_IN+XFRM_POLICY_MAX]; - si->outscnt = net->xfrm.policy_count[XFRM_POLICY_OUT+XFRM_POLICY_MAX]; - si->fwdscnt = net->xfrm.policy_count[XFRM_POLICY_FWD+XFRM_POLICY_MAX]; + si->incnt = READ_ONCE(net->xfrm.policy_count[XFRM_POLICY_IN]); + si->outcnt = READ_ONCE(net->xfrm.policy_count[XFRM_POLICY_OUT]); + si->fwdcnt = READ_ONCE(net->xfrm.policy_count[XFRM_POLICY_FWD]); + si->inscnt = READ_ONCE(net->xfrm.policy_count[XFRM_POLICY_IN+XFRM_POLICY_MAX]); + si->outscnt = READ_ONCE(net->xfrm.policy_count[XFRM_POLICY_OUT+XFRM_POLICY_MAX]); + si->fwdscnt = READ_ONCE(net->xfrm.policy_count[XFRM_POLICY_FWD+XFRM_POLICY_MAX]); si->spdhcnt = net->xfrm.policy_idx_hmask; si->spdhmcnt = xfrm_policy_hashmax; } @@ -2318,7 +2321,7 @@ static void __xfrm_policy_link(struct xfrm_policy *pol, int dir) } list_add(&pol->walk.all, &net->xfrm.policy_all); - net->xfrm.policy_count[dir]++; + WRITE_ONCE(net->xfrm.policy_count[dir], net->xfrm.policy_count[dir] + 1); xfrm_pol_hold(pol); } @@ -2337,7 +2340,7 @@ static struct xfrm_policy *__xfrm_policy_unlink(struct xfrm_policy *pol, } list_del_init(&pol->walk.all); - net->xfrm.policy_count[dir]--; + WRITE_ONCE(net->xfrm.policy_count[dir], net->xfrm.policy_count[dir] - 1); return pol; } @@ -3222,7 +3225,7 @@ struct dst_entry *xfrm_lookup_with_ifid(struct net *net, /* To accelerate a bit... */ if (!if_id && ((dst_orig->flags & DST_NOXFRM) || - !net->xfrm.policy_count[XFRM_POLICY_OUT])) + !READ_ONCE(net->xfrm.policy_count[XFRM_POLICY_OUT]))) goto nopol; xdst = xfrm_bundle_lookup(net, fl, family, dir, &xflo, if_id); @@ -3296,7 +3299,7 @@ ok: nopol: if ((!dst_orig->dev || !(dst_orig->dev->flags & IFF_LOOPBACK)) && - net->xfrm.policy_default[dir] == XFRM_USERPOLICY_BLOCK) { + READ_ONCE(net->xfrm.policy_default[dir]) == XFRM_USERPOLICY_BLOCK) { err = -EPERM; goto error; } @@ -3750,7 +3753,7 @@ int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb, const bool is_crypto_offload = sp && (xfrm_input_state(skb)->xso.type == XFRM_DEV_OFFLOAD_CRYPTO); - if (net->xfrm.policy_default[dir] == XFRM_USERPOLICY_BLOCK) { + if (READ_ONCE(net->xfrm.policy_default[dir]) == XFRM_USERPOLICY_BLOCK) { XFRM_INC_STATS(net, LINUX_MIB_XFRMINNOPOLS); return 0; } diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 898cddff498c..89daad5db10c 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -1207,9 +1207,11 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark, struct hlist_head *state_cache_input; struct xfrm_state *x = NULL; + /* BH is always disabled on the input path. */ + lockdep_assert_in_softirq(); + state_cache_input = raw_cpu_ptr(net->xfrm.state_cache_input); - rcu_read_lock(); hlist_for_each_entry_rcu(x, state_cache_input, state_cache_input) { if (x->props.family != family || x->id.spi != spi || @@ -1227,20 +1229,25 @@ struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark, xfrm_hash_ptrs_get(net, &state_ptrs); x = __xfrm_state_lookup(&state_ptrs, mark, daddr, spi, proto, family); - - if (x && x->km.state == XFRM_STATE_VALID) { - spin_lock_bh(&net->xfrm.xfrm_state_lock); - if (hlist_unhashed(&x->state_cache_input)) { + if (x) { + spin_lock(&net->xfrm.xfrm_state_lock); + if (x->km.state != XFRM_STATE_VALID) { + /* + * The state is about to be destroyed. + * + * Don't add it to the cache but still + * return it to the caller. + */ + } else if (hlist_unhashed(&x->state_cache_input)) { hlist_add_head_rcu(&x->state_cache_input, state_cache_input); } else { hlist_del_rcu(&x->state_cache_input); hlist_add_head_rcu(&x->state_cache_input, state_cache_input); } - spin_unlock_bh(&net->xfrm.xfrm_state_lock); + spin_unlock(&net->xfrm.xfrm_state_lock); } out: - rcu_read_unlock(); return x; } EXPORT_SYMBOL(xfrm_input_state_lookup); @@ -2020,6 +2027,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig, if (!x->encap) goto error; + x->mapping_maxage = orig->mapping_maxage; + x->nat_keepalive_interval = orig->nat_keepalive_interval; } if (orig->security) @@ -2054,7 +2063,6 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig, x->km.seq = orig->km.seq; x->replay = orig->replay; x->preplay = orig->preplay; - x->mapping_maxage = orig->mapping_maxage; x->lastused = orig->lastused; x->new_mapping = 0; x->new_mapping_sport = 0; @@ -2975,7 +2983,7 @@ int xfrm_user_policy(struct sock *sk, int optname, sockptr_t optval, int optlen) if (IS_ERR(data)) return PTR_ERR(data); - if (in_compat_syscall()) { + if (IS_ENABLED(CONFIG_COMPAT_FOR_U64_ALIGNMENT) && in_compat_syscall()) { struct xfrm_translator *xtr = xfrm_get_translator(); if (!xtr) { diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index 2c11866be578..9ad6619888c7 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -2476,9 +2476,9 @@ static int xfrm_notify_userpolicy(struct net *net) } up = nlmsg_data(nlh); - up->in = net->xfrm.policy_default[XFRM_POLICY_IN]; - up->fwd = net->xfrm.policy_default[XFRM_POLICY_FWD]; - up->out = net->xfrm.policy_default[XFRM_POLICY_OUT]; + up->in = READ_ONCE(net->xfrm.policy_default[XFRM_POLICY_IN]); + up->fwd = READ_ONCE(net->xfrm.policy_default[XFRM_POLICY_FWD]); + up->out = READ_ONCE(net->xfrm.policy_default[XFRM_POLICY_OUT]); nlmsg_end(skb, nlh); @@ -2502,13 +2502,13 @@ static int xfrm_set_default(struct sk_buff *skb, struct nlmsghdr *nlh, struct xfrm_userpolicy_default *up = nlmsg_data(nlh); if (xfrm_userpolicy_is_valid(up->in)) - net->xfrm.policy_default[XFRM_POLICY_IN] = up->in; + WRITE_ONCE(net->xfrm.policy_default[XFRM_POLICY_IN], up->in); if (xfrm_userpolicy_is_valid(up->fwd)) - net->xfrm.policy_default[XFRM_POLICY_FWD] = up->fwd; + WRITE_ONCE(net->xfrm.policy_default[XFRM_POLICY_FWD], up->fwd); if (xfrm_userpolicy_is_valid(up->out)) - net->xfrm.policy_default[XFRM_POLICY_OUT] = up->out; + WRITE_ONCE(net->xfrm.policy_default[XFRM_POLICY_OUT], up->out); rt_genid_bump_all(net); @@ -2538,9 +2538,9 @@ static int xfrm_get_default(struct sk_buff *skb, struct nlmsghdr *nlh, } r_up = nlmsg_data(r_nlh); - r_up->in = net->xfrm.policy_default[XFRM_POLICY_IN]; - r_up->fwd = net->xfrm.policy_default[XFRM_POLICY_FWD]; - r_up->out = net->xfrm.policy_default[XFRM_POLICY_OUT]; + r_up->in = READ_ONCE(net->xfrm.policy_default[XFRM_POLICY_IN]); + r_up->fwd = READ_ONCE(net->xfrm.policy_default[XFRM_POLICY_FWD]); + r_up->out = READ_ONCE(net->xfrm.policy_default[XFRM_POLICY_OUT]); nlmsg_end(r_skb, r_nlh); return nlmsg_unicast(net->xfrm.nlsk, r_skb, portid); @@ -3463,7 +3463,7 @@ static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, if (!netlink_net_capable(skb, CAP_NET_ADMIN)) return -EPERM; - if (in_compat_syscall()) { + if (IS_ENABLED(CONFIG_COMPAT_FOR_U64_ALIGNMENT) && in_compat_syscall()) { struct xfrm_translator *xtr = xfrm_get_translator(); if (!xtr) |
