fix #4039: backport aquantia atlantic NIC fixes
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
		
							parent
							
								
									c9fb416fa5
								
							
						
					
					
						commit
						b321611251
					
				| @ -0,0 +1,123 @@ | |||||||
|  | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: Kai-Heng Feng <kai.heng.feng@canonical.com> | ||||||
|  | Date: Fri, 8 Apr 2022 10:22:04 +0800 | ||||||
|  | Subject: [PATCH] net: atlantic: Avoid out-of-bounds indexing | ||||||
|  | 
 | ||||||
|  | [ Upstream commit 8d3a6c37d50d5a0504c126c932cc749e6dd9c78f ] | ||||||
|  | 
 | ||||||
|  | UBSAN warnings are observed on atlantic driver: | ||||||
|  | [ 294.432996] UBSAN: array-index-out-of-bounds in /build/linux-Qow4fL/linux-5.15.0/drivers/net/ethernet/aquantia/atlantic/aq_nic.c:484:48 | ||||||
|  | [ 294.433695] index 8 is out of range for type 'aq_vec_s *[8]' | ||||||
|  | 
 | ||||||
|  | The ring is dereferenced right before breaking out the loop, to prevent | ||||||
|  | that from happening, only use the index in the loop to fix the issue. | ||||||
|  | 
 | ||||||
|  | BugLink: https://bugs.launchpad.net/bugs/1958770 | ||||||
|  | Tested-by: Mario Limonciello <mario.limonciello@amd.com> | ||||||
|  | Signed-off-by: Kai-Heng Feng <kai.heng.feng@canonical.com> | ||||||
|  | Reviewed-by: Igor Russkikh <irusskikh@marvell.com> | ||||||
|  | Link: https://lore.kernel.org/r/20220408022204.16815-1-kai.heng.feng@canonical.com | ||||||
|  | Signed-off-by: Jakub Kicinski <kuba@kernel.org> | ||||||
|  | Signed-off-by: Sasha Levin <sashal@kernel.org> | ||||||
|  | Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com> | ||||||
|  | ---
 | ||||||
|  |  .../net/ethernet/aquantia/atlantic/aq_nic.c   |  8 +++---- | ||||||
|  |  .../net/ethernet/aquantia/atlantic/aq_vec.c   | 24 +++++++++---------- | ||||||
|  |  2 files changed, 16 insertions(+), 16 deletions(-) | ||||||
|  | 
 | ||||||
|  | diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_nic.c b/drivers/net/ethernet/aquantia/atlantic/aq_nic.c
 | ||||||
|  | index 9de0065f89b9..fbb1e05d5878 100644
 | ||||||
|  | --- a/drivers/net/ethernet/aquantia/atlantic/aq_nic.c
 | ||||||
|  | +++ b/drivers/net/ethernet/aquantia/atlantic/aq_nic.c
 | ||||||
|  | @@ -480,8 +480,8 @@ int aq_nic_start(struct aq_nic_s *self)
 | ||||||
|  |  	if (err < 0) | ||||||
|  |  		goto err_exit; | ||||||
|  |   | ||||||
|  | -	for (i = 0U, aq_vec = self->aq_vec[0];
 | ||||||
|  | -		self->aq_vecs > i; ++i, aq_vec = self->aq_vec[i]) {
 | ||||||
|  | +	for (i = 0U; self->aq_vecs > i; ++i) {
 | ||||||
|  | +		aq_vec = self->aq_vec[i];
 | ||||||
|  |  		err = aq_vec_start(aq_vec); | ||||||
|  |  		if (err < 0) | ||||||
|  |  			goto err_exit; | ||||||
|  | @@ -511,8 +511,8 @@ int aq_nic_start(struct aq_nic_s *self)
 | ||||||
|  |  		mod_timer(&self->polling_timer, jiffies + | ||||||
|  |  			  AQ_CFG_POLLING_TIMER_INTERVAL); | ||||||
|  |  	} else { | ||||||
|  | -		for (i = 0U, aq_vec = self->aq_vec[0];
 | ||||||
|  | -			self->aq_vecs > i; ++i, aq_vec = self->aq_vec[i]) {
 | ||||||
|  | +		for (i = 0U; self->aq_vecs > i; ++i) {
 | ||||||
|  | +			aq_vec = self->aq_vec[i];
 | ||||||
|  |  			err = aq_pci_func_alloc_irq(self, i, self->ndev->name, | ||||||
|  |  						    aq_vec_isr, aq_vec, | ||||||
|  |  						    aq_vec_get_affinity_mask(aq_vec)); | ||||||
|  | diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_vec.c b/drivers/net/ethernet/aquantia/atlantic/aq_vec.c
 | ||||||
|  | index f4774cf051c9..6ab1f3212d24 100644
 | ||||||
|  | --- a/drivers/net/ethernet/aquantia/atlantic/aq_vec.c
 | ||||||
|  | +++ b/drivers/net/ethernet/aquantia/atlantic/aq_vec.c
 | ||||||
|  | @@ -43,8 +43,8 @@ static int aq_vec_poll(struct napi_struct *napi, int budget)
 | ||||||
|  |  	if (!self) { | ||||||
|  |  		err = -EINVAL; | ||||||
|  |  	} else { | ||||||
|  | -		for (i = 0U, ring = self->ring[0];
 | ||||||
|  | -			self->tx_rings > i; ++i, ring = self->ring[i]) {
 | ||||||
|  | +		for (i = 0U; self->tx_rings > i; ++i) {
 | ||||||
|  | +			ring = self->ring[i];
 | ||||||
|  |  			u64_stats_update_begin(&ring[AQ_VEC_RX_ID].stats.rx.syncp); | ||||||
|  |  			ring[AQ_VEC_RX_ID].stats.rx.polls++; | ||||||
|  |  			u64_stats_update_end(&ring[AQ_VEC_RX_ID].stats.rx.syncp); | ||||||
|  | @@ -182,8 +182,8 @@ int aq_vec_init(struct aq_vec_s *self, const struct aq_hw_ops *aq_hw_ops,
 | ||||||
|  |  	self->aq_hw_ops = aq_hw_ops; | ||||||
|  |  	self->aq_hw = aq_hw; | ||||||
|  |   | ||||||
|  | -	for (i = 0U, ring = self->ring[0];
 | ||||||
|  | -		self->tx_rings > i; ++i, ring = self->ring[i]) {
 | ||||||
|  | +	for (i = 0U; self->tx_rings > i; ++i) {
 | ||||||
|  | +		ring = self->ring[i];
 | ||||||
|  |  		err = aq_ring_init(&ring[AQ_VEC_TX_ID], ATL_RING_TX); | ||||||
|  |  		if (err < 0) | ||||||
|  |  			goto err_exit; | ||||||
|  | @@ -224,8 +224,8 @@ int aq_vec_start(struct aq_vec_s *self)
 | ||||||
|  |  	unsigned int i = 0U; | ||||||
|  |  	int err = 0; | ||||||
|  |   | ||||||
|  | -	for (i = 0U, ring = self->ring[0];
 | ||||||
|  | -		self->tx_rings > i; ++i, ring = self->ring[i]) {
 | ||||||
|  | +	for (i = 0U; self->tx_rings > i; ++i) {
 | ||||||
|  | +		ring = self->ring[i];
 | ||||||
|  |  		err = self->aq_hw_ops->hw_ring_tx_start(self->aq_hw, | ||||||
|  |  							&ring[AQ_VEC_TX_ID]); | ||||||
|  |  		if (err < 0) | ||||||
|  | @@ -248,8 +248,8 @@ void aq_vec_stop(struct aq_vec_s *self)
 | ||||||
|  |  	struct aq_ring_s *ring = NULL; | ||||||
|  |  	unsigned int i = 0U; | ||||||
|  |   | ||||||
|  | -	for (i = 0U, ring = self->ring[0];
 | ||||||
|  | -		self->tx_rings > i; ++i, ring = self->ring[i]) {
 | ||||||
|  | +	for (i = 0U; self->tx_rings > i; ++i) {
 | ||||||
|  | +		ring = self->ring[i];
 | ||||||
|  |  		self->aq_hw_ops->hw_ring_tx_stop(self->aq_hw, | ||||||
|  |  						 &ring[AQ_VEC_TX_ID]); | ||||||
|  |   | ||||||
|  | @@ -268,8 +268,8 @@ void aq_vec_deinit(struct aq_vec_s *self)
 | ||||||
|  |  	if (!self) | ||||||
|  |  		goto err_exit; | ||||||
|  |   | ||||||
|  | -	for (i = 0U, ring = self->ring[0];
 | ||||||
|  | -		self->tx_rings > i; ++i, ring = self->ring[i]) {
 | ||||||
|  | +	for (i = 0U; self->tx_rings > i; ++i) {
 | ||||||
|  | +		ring = self->ring[i];
 | ||||||
|  |  		aq_ring_tx_clean(&ring[AQ_VEC_TX_ID]); | ||||||
|  |  		aq_ring_rx_deinit(&ring[AQ_VEC_RX_ID]); | ||||||
|  |  	} | ||||||
|  | @@ -297,8 +297,8 @@ void aq_vec_ring_free(struct aq_vec_s *self)
 | ||||||
|  |  	if (!self) | ||||||
|  |  		goto err_exit; | ||||||
|  |   | ||||||
|  | -	for (i = 0U, ring = self->ring[0];
 | ||||||
|  | -		self->tx_rings > i; ++i, ring = self->ring[i]) {
 | ||||||
|  | +	for (i = 0U; self->tx_rings > i; ++i) {
 | ||||||
|  | +		ring = self->ring[i];
 | ||||||
|  |  		aq_ring_free(&ring[AQ_VEC_TX_ID]); | ||||||
|  |  		if (i < self->rx_rings) | ||||||
|  |  			aq_ring_free(&ring[AQ_VEC_RX_ID]); | ||||||
| @ -0,0 +1,106 @@ | |||||||
|  | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: Manuel Ullmann <labre@posteo.de> | ||||||
|  | Date: Mon, 18 Apr 2022 00:20:01 +0200 | ||||||
|  | Subject: [PATCH] net: atlantic: invert deep par in pm functions, preventing | ||||||
|  |  null derefs | ||||||
|  | 
 | ||||||
|  | commit cbe6c3a8f8f4315b96e46e1a1c70393c06d95a4c upstream. | ||||||
|  | 
 | ||||||
|  | This will reset deeply on freeze and thaw instead of suspend and | ||||||
|  | resume and prevent null pointer dereferences of the uninitialized ring | ||||||
|  | 0 buffer while thawing. | ||||||
|  | 
 | ||||||
|  | The impact is an indefinitely hanging kernel. You can't switch | ||||||
|  | consoles after this and the only possible user interaction is SysRq. | ||||||
|  | 
 | ||||||
|  | BUG: kernel NULL pointer dereference | ||||||
|  | RIP: 0010:aq_ring_rx_fill+0xcf/0x210 [atlantic] | ||||||
|  | aq_vec_init+0x85/0xe0 [atlantic] | ||||||
|  | aq_nic_init+0xf7/0x1d0 [atlantic] | ||||||
|  | atl_resume_common+0x4f/0x100 [atlantic] | ||||||
|  | pci_pm_thaw+0x42/0xa0 | ||||||
|  | 
 | ||||||
|  | resolves in aq_ring.o to | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | 0000000000000ae0 <aq_ring_rx_fill>: | ||||||
|  | { | ||||||
|  | /* ... */ | ||||||
|  |  baf:	48 8b 43 08          	mov    0x8(%rbx),%rax | ||||||
|  |  		buff->flags = 0U; /* buff is NULL */ | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The bug has been present since the introduction of the new pm code in | ||||||
|  | 8aaa112a57c1 ("net: atlantic: refactoring pm logic") and was hidden | ||||||
|  | until 8ce84271697a ("net: atlantic: changes for multi-TC support"), | ||||||
|  | which refactored the aq_vec_{free,alloc} functions into | ||||||
|  | aq_vec_{,ring}_{free,alloc}, but is technically not wrong. The | ||||||
|  | original functions just always reinitialized the buffers on S3/S4. If | ||||||
|  | the interface is down before freezing, the bug does not occur. It does | ||||||
|  | not matter, whether the initrd contains and loads the module before | ||||||
|  | thawing. | ||||||
|  | 
 | ||||||
|  | So the fix is to invert the boolean parameter deep in all pm function | ||||||
|  | calls, which was clearly intended to be set like that. | ||||||
|  | 
 | ||||||
|  | First report was on Github [1], which you have to guess from the | ||||||
|  | resume logs in the posted dmesg snippet. Recently I posted one on | ||||||
|  | Bugzilla [2], since I did not have an AQC device so far. | ||||||
|  | 
 | ||||||
|  | #regzbot introduced: 8ce84271697a | ||||||
|  | #regzbot from: koo5 <kolman.jindrich@gmail.com> | ||||||
|  | #regzbot monitor: https://github.com/Aquantia/AQtion/issues/32 | ||||||
|  | 
 | ||||||
|  | Fixes: 8aaa112a57c1 ("net: atlantic: refactoring pm logic") | ||||||
|  | Link: https://github.com/Aquantia/AQtion/issues/32 [1] | ||||||
|  | Link: https://bugzilla.kernel.org/show_bug.cgi?id=215798 [2] | ||||||
|  | Cc: stable@vger.kernel.org | ||||||
|  | Reported-by: koo5 <kolman.jindrich@gmail.com> | ||||||
|  | Signed-off-by: Manuel Ullmann <labre@posteo.de> | ||||||
|  | Signed-off-by: David S. Miller <davem@davemloft.net> | ||||||
|  | Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> | ||||||
|  | Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com> | ||||||
|  | ---
 | ||||||
|  |  .../ethernet/aquantia/atlantic/aq_pci_func.c  | 20 +++++++++---------- | ||||||
|  |  1 file changed, 10 insertions(+), 10 deletions(-) | ||||||
|  | 
 | ||||||
|  | diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_pci_func.c b/drivers/net/ethernet/aquantia/atlantic/aq_pci_func.c
 | ||||||
|  | index 797a95142d1f..3a529ee8c834 100644
 | ||||||
|  | --- a/drivers/net/ethernet/aquantia/atlantic/aq_pci_func.c
 | ||||||
|  | +++ b/drivers/net/ethernet/aquantia/atlantic/aq_pci_func.c
 | ||||||
|  | @@ -443,25 +443,25 @@ static int atl_resume_common(struct device *dev, bool deep)
 | ||||||
|  |  } | ||||||
|  |   | ||||||
|  |  static int aq_pm_freeze(struct device *dev) | ||||||
|  | -{
 | ||||||
|  | -	return aq_suspend_common(dev, false);
 | ||||||
|  | -}
 | ||||||
|  | -
 | ||||||
|  | -static int aq_pm_suspend_poweroff(struct device *dev)
 | ||||||
|  |  { | ||||||
|  |  	return aq_suspend_common(dev, true); | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | +static int aq_pm_suspend_poweroff(struct device *dev)
 | ||||||
|  | +{
 | ||||||
|  | +	return aq_suspend_common(dev, false);
 | ||||||
|  | +}
 | ||||||
|  | +
 | ||||||
|  |  static int aq_pm_thaw(struct device *dev) | ||||||
|  | -{
 | ||||||
|  | -	return atl_resume_common(dev, false);
 | ||||||
|  | -}
 | ||||||
|  | -
 | ||||||
|  | -static int aq_pm_resume_restore(struct device *dev)
 | ||||||
|  |  { | ||||||
|  |  	return atl_resume_common(dev, true); | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | +static int aq_pm_resume_restore(struct device *dev)
 | ||||||
|  | +{
 | ||||||
|  | +	return atl_resume_common(dev, false);
 | ||||||
|  | +}
 | ||||||
|  | +
 | ||||||
|  |  static const struct dev_pm_ops aq_pm_ops = { | ||||||
|  |  	.suspend = aq_pm_suspend_poweroff, | ||||||
|  |  	.poweroff = aq_pm_suspend_poweroff, | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Thomas Lamprecht
						Thomas Lamprecht