Similar to the skb path issue explained in the previous change, ENETC could end up transmitting short frames coming from XDP. The way in which this could happen is a bit contrived, but it involves XDP_REDIRECT from a veth interface pair. As for enetc_xmit(), there are two separate limitations for the overall FRM_LEN and for the head BUFF_LEN. For the head BUFF_LEN, we add a direct restriction in enetc_xdp_xmit(), and for the overall FRM_LEN, we introduce a xdp_frame_pad() best-effort generic helper which we call from the same place. This helper alters the frame, but that should be safe, because ndo_xdp_xmit() is the hand-off function where the XDP frames become the responsibility of the driver. AFAIU, struct xdp_frame doesn't have multiple copies. I say best-effort because xdp_frame_pad() can only expand the head buffer of an XDP frame. It cannot expand the last fragment of a multi-buffer XDP frame, because, unlike bpf_xdp_frags_increase_tail(), it lacks access to the rxq->frag_size, aka the capacity of the chunk of memory being pointed to by the fragment. So, if the frame happens to be less than minimum Ethernet size, but fragmented, callers of this function will have to drop it. Fixes: 9d2b68cc108d ("net: enetc: add support for XDP_REDIRECT") Signed-off-by: Vladimir Oltean --- v1->v2: - handle multi-buffer frames instead of being unaware of their multi-buffer quality - add separate restriction for BUFF_LEN - increment drop counter --- drivers/net/ethernet/freescale/enetc/enetc.c | 10 ++++++++- include/net/xdp.h | 23 ++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc.c b/drivers/net/ethernet/freescale/enetc/enetc.c index c70df45422e0..196b7828e2aa 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc.c +++ b/drivers/net/ethernet/freescale/enetc/enetc.c @@ -1816,9 +1816,17 @@ int enetc_xdp_xmit(struct net_device *ndev, int num_frames, prefetchw(ENETC_TXBD(*tx_ring, tx_ring->next_to_use)); for (k = 0; k < num_frames; k++) { + struct xdp_frame *xdpf = frames[k]; + + if (unlikely(xdp_frame_pad(xdpf) || + xdpf->len < ENETC_MIN_BUFF_SIZE)) { + tx_ring->stats.xdp_tx_drops++; + break; + } + xdp_tx_bd_cnt = enetc_xdp_frame_to_xdp_tx_swbd(tx_ring, xdp_redirect_arr, - frames[k]); + xdpf); if (unlikely(xdp_tx_bd_cnt < 0)) { tx_ring->stats.xdp_tx_drops++; break; diff --git a/include/net/xdp.h b/include/net/xdp.h index aa742f413c35..276afc9aa21d 100644 --- a/include/net/xdp.h +++ b/include/net/xdp.h @@ -477,6 +477,29 @@ xdp_get_frame_len(const struct xdp_frame *xdpf) return len; } +static inline int xdp_frame_pad(struct xdp_frame *xdpf) +{ + unsigned int total_len, pad; + void *sinfo; + + total_len = xdp_get_frame_len(xdpf); + if (likely(total_len >= ETH_ZLEN)) + return 0; + + if (unlikely(xdp_frame_has_frags(xdpf))) + return -EOPNOTSUPP; + + pad = ETH_ZLEN - total_len; + sinfo = xdp_get_shared_info_from_frame(xdpf); + if (unlikely(xdpf->data + xdpf->len + pad > sinfo)) + return -ENOMEM; + + memset(xdpf->data + xdpf->len, 0, pad); + xdpf->len += pad; + + return 0; +} + int __xdp_rxq_info_reg(struct xdp_rxq_info *xdp_rxq, struct net_device *dev, u32 queue_index, unsigned int napi_id, u32 frag_size); -- 2.43.0