diff options
author | Michael Brown <mcb30@ipxe.org> | 2021-04-10 16:53:52 +0100 |
---|---|---|
committer | Michael Brown <mcb30@ipxe.org> | 2021-04-10 16:53:52 +0100 |
commit | 78749542fc6d31ad41ecc350dea364e21362f985 (patch) | |
tree | 8ba0a9148d7a3fa3addc9a63e9c69584116b8c4f | |
parent | 0be8491b717fec6697dbf6ef8ac07604e062ecb1 (diff) | |
download | ipxe-78749542fc6d31ad41ecc350dea364e21362f985.tar.gz |
[netdevice] Ensure driver transmit() and poll() will not be re-entered
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>
-rw-r--r-- | src/include/ipxe/netdevice.h | 6 | ||||
-rw-r--r-- | src/net/netdevice.c | 39 |
2 files changed, 38 insertions, 7 deletions
diff --git a/src/include/ipxe/netdevice.h b/src/include/ipxe/netdevice.h index b9c651c71..294f7b367 100644 --- a/src/include/ipxe/netdevice.h +++ b/src/include/ipxe/netdevice.h @@ -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" ) diff --git a/src/net/netdevice.c b/src/net/netdevice.c index 6e685630c..5df306e8d 100644 --- a/src/net/netdevice.c +++ b/src/net/netdevice.c @@ -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; } /** |