]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[netdevice] Ensure driver transmit() and poll() will not be re-entered
authorMichael Brown <mcb30@ipxe.org>
Sat, 10 Apr 2021 15:53:52 +0000 (16:53 +0100)
committerMichael Brown <mcb30@ipxe.org>
Sat, 10 Apr 2021 15:53:52 +0000 (16:53 +0100)
When CONSOLE_SYSLOG is used, a DBG() from within a network device
driver may cause its transmit() or poll() methods to be unexpectedly
re-entered.  Since these methods are not intended to be re-entrant,
this can lead to undefined behaviour.

Add an explicit re-entrancy guard to both methods.  Note that this
must operate at a per-netdevice level, since there are legitimate
circumstances under which the netdev_tx() or netdev_poll() functions
may be re-entered (e.g. when using VLAN devices).

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/netdevice.h
src/net/netdevice.c

index b9c651c71fe112915c025b58f9b2febbd561f86d..294f7b3673142adaabeac134b121c1dbcd84a95c 100644 (file)
@@ -451,6 +451,12 @@ struct net_device {
  */
 #define NETDEV_IRQ_UNSUPPORTED 0x0008
 
+/** Network device transmission is in progress */
+#define NETDEV_TX_IN_PROGRESS 0x0010
+
+/** Network device poll is in progress */
+#define NETDEV_POLL_IN_PROGRESS 0x0020
+
 /** Link-layer protocol table */
 #define LL_PROTOCOLS __table ( struct ll_protocol, "ll_protocols" )
 
index 6e685630c6c351b2b331c98d66b5d9c0e3b04ae0..5df306e8d69efead21b00c2d4517452421be4655 100644 (file)
@@ -297,30 +297,45 @@ int netdev_tx ( struct net_device *netdev, struct io_buffer *iobuf ) {
        /* Enqueue packet */
        list_add_tail ( &iobuf->list, &netdev->tx_queue );
 
+       /* Guard against re-entry */
+       if ( netdev->state & NETDEV_TX_IN_PROGRESS ) {
+               rc = -EBUSY;
+               goto err_busy;
+       }
+       netdev->state |= NETDEV_TX_IN_PROGRESS;
+
        /* Avoid calling transmit() on unopened network devices */
        if ( ! netdev_is_open ( netdev ) ) {
                rc = -ENETUNREACH;
-               goto err;
+               goto err_closed;
        }
 
        /* Discard packet (for test purposes) if applicable */
        if ( ( rc = inject_fault ( NETDEV_DISCARD_RATE ) ) != 0 )
-               goto err;
+               goto err_fault;
 
        /* Map for DMA, if required */
        if ( netdev->dma && ( ! dma_mapped ( &iobuf->map ) ) ) {
                if ( ( rc = iob_map_tx ( iobuf, netdev->dma ) ) != 0 )
-                       goto err;
+                       goto err_map;
        }
 
        /* Transmit packet */
        if ( ( rc = netdev->op->transmit ( netdev, iobuf ) ) != 0 )
-               goto err;
+               goto err_transmit;
+
+       /* Clear in-progress flag */
+       netdev->state &= ~NETDEV_TX_IN_PROGRESS;
 
        profile_stop ( &net_tx_profiler );
        return 0;
 
- err:
+ err_transmit:
+ err_map:
+ err_fault:
+ err_closed:
+       netdev->state &= ~NETDEV_TX_IN_PROGRESS;
+ err_busy:
        netdev_tx_complete_err ( netdev, iobuf, rc );
        return rc;
 }
@@ -552,8 +567,18 @@ void netdev_rx_err ( struct net_device *netdev,
  */
 void netdev_poll ( struct net_device *netdev ) {
 
-       if ( netdev_is_open ( netdev ) )
-               netdev->op->poll ( netdev );
+       /* Avoid calling poll() on unopened network devices */
+       if ( ! netdev_is_open ( netdev ) )
+               return;
+
+       /* Guard against re-entry */
+       if ( netdev->state & NETDEV_POLL_IN_PROGRESS )
+               return;
+
+       /* Poll device */
+       netdev->state |= NETDEV_POLL_IN_PROGRESS;
+       netdev->op->poll ( netdev );
+       netdev->state &= ~NETDEV_POLL_IN_PROGRESS;
 }
 
 /**