Netdev List
 help / color / mirror / Atom feed
From: Jakub Kicinski <kuba@kernel.org>
To: eric.joyner@amd.com
Cc: Jakub Kicinski <kuba@kernel.org>,
	netdev@vger.kernel.org, brett.creeley@amd.com,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	pabeni@redhat.com, nikhil.rao@amd.com
Subject: Re: [PATCH net 1/3] ionic: check for a NULL port_info in the remaining ethtool ops
Date: Tue, 18 Aug 2026 09:58:35 -0700	[thread overview]
Message-ID: <20260818165835.4009139-1-kuba@kernel.org> (raw)
In-Reply-To: <20260814-ionic-port-info-lifetime-v1-1-f73b1a06c5f6@amd.com>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ionic: check for a NULL port_info in the remaining ethtool ops

This adds !idev->port_info checks to the ethtool ops that dereferenced the
coherent port_info DMA buffer without testing it, since ionic_port_init()
frees the buffer and stores NULL on device command failure while the netdev
is still registered.  The ops return -EOPNOTSUPP (or return early, for
ionic_get_pauseparam()) when the pointer is NULL.

A few questions below.

> ionic_get_link_ext_stats() and ionic_get_link_ksettings() already test
> the pointer before using it, but the rest of the ethtool ops dereference
> it blindly, so an unprivileged "ethtool --show-fec eth0" can oops after
> a failed firmware recovery.
>
> Add the same check to the ops that were missing it.

[Severity: High]
This isn't a bug introduced by this patch, but does the "rest of the
ethtool ops" also include .get_ethtool_stats?  That path still reaches an
unguarded dereference:

ionic_get_stats()            (ionic_ethtool.c)
 -> ionic_sw_stats_get_values()  (ionic_stats.c)
      port_stats = &lif->ionic->idev.port_info->stats;
      for (i = 0; i < IONIC_NUM_PORT_STATS; i++) {
              **buf = IONIC_READ_STAT_LE64(port_stats,
                                           &ionic_port_stats_desc[i]);

The only gate is in ionic_get_stats():

	if (test_bit(IONIC_LIF_F_FW_RESET, lif->state))
		return;

ionic_extra_port_stats_get_count() in the same file handles the same state
explicitly for the same ethtool -S request:

	if (!idev->port_info) {
		memset(pes_cache, 0xff, sizeof(*pes_cache));
		return count;
	}

so the two consumers of the same buffer disagree about whether the pointer
can be NULL.  Note also that ionic_remove() does
test_and_clear_bit(IONIC_LIF_F_FW_RESET, ...) before the netdev is
unregistered via ionic_lif_unregister(), so after a failed
ionic_reset_done() -> ionic_setup_one() -> ionic_port_init() the flag can be
clear, port_info NULL and the netdev still registered.  Should
ionic_sw_stats_get_values() get the same test the other ops just received?

> diff --git a/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c b/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c
> index c4ab4b5caa0ae..0830422fe7bae 100644
> --- a/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c
> +++ b/drivers/net/ethernet/pensando/ionic/ionic_ethtool.c
> @@ -347,6 +347,11 @@ static int ionic_set_link_ksettings(struct net_device *netdev,
>  	if (test_bit(IONIC_LIF_F_FW_RESET, lif->state))
>  		return -EBUSY;
>  
> +	if (!idev->port_info) {
> +		netdev_err(netdev, "port_info not initialized\n");
> +		return -EOPNOTSUPP;
> +	}
> +
>  	/* set autoneg */
>  	if (ks->base.autoneg != idev->port_info->config.an_enable) {
>  		mutex_lock(&ionic->dev_cmd_lock);

[Severity: High]
This isn't a bug introduced by this patch, since these ops previously
dereferenced port_info unconditionally, but can the added test still be
raced by the writer?  In ionic_port_init() the free and the NULL store
happen after the lock is dropped:

drivers/net/ethernet/pensando/ionic/ionic_main.c:ionic_port_init() {
	...
	mutex_unlock(&ionic->dev_cmd_lock);
	if (err) {
		dev_err(ionic->dev, "Failed to init port\n");
		dma_free_coherent(ionic->dev, idev->port_info_sz,
				  idev->port_info, idev->port_info_pa);
		idev->port_info = NULL;
	...
}

and its callers, ionic_lif_deferred_work() -> ionic_lif_handle_fw_up() and
ionic_reset_done() -> ionic_setup_one(), hold neither rtnl nor the netdev
instance lock that the ethtool ops run under.

In ionic_set_link_ksettings() the second dereference is separated from the
new test by a sleeping devcmd wait that releases the only lock the writer
takes:

	if (ks->base.autoneg != idev->port_info->config.an_enable) {
		mutex_lock(&ionic->dev_cmd_lock);
		ionic_dev_cmd_port_autoneg(idev, ks->base.autoneg);
		err = ionic_dev_cmd_wait(ionic, DEVCMD_TIMEOUT);
		mutex_unlock(&ionic->dev_cmd_lock);
	...
	/* set speed */
	if (ks->base.speed != le32_to_cpu(idev->port_info->config.speed)) {

Can the deferred fw-up work free the buffer and store NULL while the caller
sleeps in ionic_dev_cmd_wait(), so that the speed comparison dereferences
NULL?  ionic_set_fecparam() has the same shape with its two dereferences of
config.fec_type and config.an_enable, and ionic_get_module_eeprom_by_page()
computes src into the buffer after the test and then copies from it in
ionic_do_module_copy(), which would hand freed coherent memory to user
space.

> @@ -378,6 +383,11 @@ static void ionic_get_pauseparam(struct net_device *netdev,
>  
>  	pause->autoneg = 0;
>  
> +	if (!lif->ionic->idev.port_info) {
> +		netdev_err_once(netdev, "port_info not initialized\n");
> +		return;
> +	}
> +
>  	pause_type = lif->ionic->idev.port_info->config.pause_type;
>  	if (pause_type) {
>  		pause->rx_pause = (pause_type & IONIC_PAUSE_F_RX) ? 1 : 0;

[ ... ]

> @@ -424,6 +439,11 @@ static int ionic_get_fecparam(struct net_device *netdev,
>  {
>  	struct ionic_lif *lif = netdev_priv(netdev);
>  
> +	if (!lif->ionic->idev.port_info) {
> +		netdev_err(netdev, "port_info not initialized\n");
> +		return -EOPNOTSUPP;
> +	}
> +

[Severity: Low]
This isn't a bug, but is the difference in rate limiting between the two
getters intentional?  ionic_get_pauseparam() above uses netdev_err_once()
for this message, matching ionic_get_link_ext_stats(), while
ionic_get_fecparam() uses a plain netdev_err() for the identical message
and condition.  Both are unprivileged read-only ops, so a loop of
"ethtool --show-fec eth0" prints one KERN_ERR line per call while
port_info is NULL.

>  	switch (lif->ionic->idev.port_info->config.fec_type) {
>  	case IONIC_PORT_FEC_TYPE_NONE:
>  		fec->active_fec = ETHTOOL_FEC_OFF;
> @@ -451,6 +471,11 @@ static int ionic_set_fecparam(struct net_device *netdev,
>  	if (test_bit(IONIC_LIF_F_FW_RESET, lif->state))
>  		return -EBUSY;
>  
> +	if (!lif->ionic->idev.port_info) {
> +		netdev_err(netdev, "port_info not initialized\n");
> +		return -EOPNOTSUPP;
> +	}
> +
>  	if (lif->ionic->idev.port_info->config.an_enable) {
>  		netdev_err(netdev, "FEC request not allowed while autoneg is enabled\n");
>  		return -EINVAL;
> @@ -1004,6 +1029,11 @@ static int ionic_get_module_eeprom_by_page(struct net_device *netdev,
>  		return -EINVAL;
>  	}
>  
> +	if (!idev->port_info) {
> +		NL_SET_ERR_MSG_MOD(extack, "port_info not initialized");
> +		return -EOPNOTSUPP;
> +	}
> +
>  	switch (page_data->page) {
>  	case 0:
>  		src = &idev->port_info->status.xcvr.sprom[page_data->offset];

One more question about the failure path the commit message describes,
which the patch does not touch.

[Severity: Medium]
This is a pre-existing issue and not introduced by this patch, but is the
teardown in the ionic_port_init() error path complete?  The buffer address
is published to the device by ionic_dev_cmd_port_init():

	.port_init.info_pa = cpu_to_le64(idev->port_info_pa),

and on failure the buffer is released without an IONIC_CMD_PORT_RESET:

drivers/net/ethernet/pensando/ionic/ionic_main.c:ionic_port_init() {
	...
	ionic_dev_cmd_port_state(&ionic->idev, IONIC_PORT_ADMIN_STATE_UP);
	ionic_dev_cmd_wait(ionic, DEVCMD_TIMEOUT);

	mutex_unlock(&ionic->dev_cmd_lock);
	if (err) {
		dev_err(ionic->dev, "Failed to init port\n");
		dma_free_coherent(ionic->dev, idev->port_info_sz,
				  idev->port_info, idev->port_info_pa);
	...
}

ionic_port_reset() does the opposite ordering whenever the firmware is
still alive:

	if (ionic_is_fw_running(idev)) {
		mutex_lock(&ionic->dev_cmd_lock);
		ionic_dev_cmd_port_reset(idev);
		err = ionic_dev_cmd_wait(ionic, DEVCMD_TIMEOUT);
		mutex_unlock(&ionic->dev_cmd_lock);
	}

	dma_free_coherent(ionic->dev, idev->port_info_sz,
			  idev->port_info, idev->port_info_pa);

On the -ETIMEDOUT path __ionic_dev_cmd_wait() only calls
ionic_dev_cmd_clean() with no abort handshake, so can a slow-but-alive
firmware still write struct ionic_port_info into pages already returned to
the coherent allocator?  This depends on firmware behaviour, so it may well
be a non-issue.  Separately, the return value of the second devcmd
(PORT_STATE = IONIC_PORT_ADMIN_STATE_UP) is discarded just before the
free; is that deliberate?
-- 
pw-bot: cr

  parent reply	other threads:[~2026-08-18 16:58 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-15  0:00 [PATCH net 0/3] ionic: fix port_info lifetime problems around device reset Eric Joyner
2026-08-15  0:00 ` [PATCH net 1/3] ionic: check for a NULL port_info in the remaining ethtool ops Eric Joyner
2026-08-15 22:39   ` Vadim Fedorenko
2026-08-18 16:58   ` Jakub Kicinski [this message]
2026-08-15  0:00 ` [PATCH net 2/3] ionic: detach the netdev in the PCI reset handler Eric Joyner
2026-08-15 22:43   ` Vadim Fedorenko
2026-08-18 16:58   ` Jakub Kicinski
2026-08-15  0:00 ` [PATCH net 3/3] ionic: free port_info when probe fails after the port is set up Eric Joyner
2026-08-15 22:49   ` Vadim Fedorenko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260818165835.4009139-1-kuba@kernel.org \
    --to=kuba@kernel.org \
    --cc=andrew+netdev@lunn.ch \
    --cc=brett.creeley@amd.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=eric.joyner@amd.com \
    --cc=netdev@vger.kernel.org \
    --cc=nikhil.rao@amd.com \
    --cc=pabeni@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox