Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ethtool: Fix wrong mod state in case of verbose and no_mask bitset

A bitset without mask in a _SET request means we want exactly the bits in
the bitset to be set. This works correctly for compact format but when
verbose format is parsed, ethnl_update_bitset32_verbose() only sets the
bits present in the request bitset but does not clear the rest. The commit
6699170376ab ("ethtool: fix application of verbose no_mask bitset") fixes
this issue by clearing the whole target bitmap before we start iterating.
The solution proposed brought an issue with the behavior of the mod
variable. As the bitset is always cleared the old value will always
differ to the new value.

Fix it by adding a new function to compare bitmaps and a temporary variable
which save the state of the old bitmap.

Fixes: 6699170376ab ("ethtool: fix application of verbose no_mask bitset")
Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
Link: https://patch.msgid.link/20241202153358.1142095-1-kory.maincent@bootlin.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Kory Maincent and committed by
Jakub Kicinski
910c4788 50b94204

+44 -4
+44 -4
net/ethtool/bitset.c
··· 425 425 return 0; 426 426 } 427 427 428 + /** 429 + * ethnl_bitmap32_equal() - Compare two bitmaps 430 + * @map1: first bitmap 431 + * @map2: second bitmap 432 + * @nbits: bit size to compare 433 + * 434 + * Return: true if first @nbits are equal, false if not 435 + */ 436 + static bool ethnl_bitmap32_equal(const u32 *map1, const u32 *map2, 437 + unsigned int nbits) 438 + { 439 + if (memcmp(map1, map2, nbits / 32 * sizeof(u32))) 440 + return false; 441 + if (nbits % 32 == 0) 442 + return true; 443 + return !((map1[nbits / 32] ^ map2[nbits / 32]) & 444 + ethnl_lower_bits(nbits % 32)); 445 + } 446 + 428 447 static int 429 448 ethnl_update_bitset32_verbose(u32 *bitmap, unsigned int nbits, 430 449 const struct nlattr *attr, struct nlattr **tb, 431 450 ethnl_string_array_t names, 432 451 struct netlink_ext_ack *extack, bool *mod) 433 452 { 453 + u32 *saved_bitmap = NULL; 434 454 struct nlattr *bit_attr; 435 455 bool no_mask; 436 456 int rem; ··· 468 448 } 469 449 470 450 no_mask = tb[ETHTOOL_A_BITSET_NOMASK]; 471 - if (no_mask) 472 - ethnl_bitmap32_clear(bitmap, 0, nbits, mod); 451 + if (no_mask) { 452 + unsigned int nwords = DIV_ROUND_UP(nbits, 32); 453 + unsigned int nbytes = nwords * sizeof(u32); 454 + bool dummy; 455 + 456 + /* The bitmap size is only the size of the map part without 457 + * its mask part. 458 + */ 459 + saved_bitmap = kcalloc(nwords, sizeof(u32), GFP_KERNEL); 460 + if (!saved_bitmap) 461 + return -ENOMEM; 462 + memcpy(saved_bitmap, bitmap, nbytes); 463 + ethnl_bitmap32_clear(bitmap, 0, nbits, &dummy); 464 + } 473 465 474 466 nla_for_each_nested(bit_attr, tb[ETHTOOL_A_BITSET_BITS], rem) { 475 467 bool old_val, new_val; ··· 490 458 if (nla_type(bit_attr) != ETHTOOL_A_BITSET_BITS_BIT) { 491 459 NL_SET_ERR_MSG_ATTR(extack, bit_attr, 492 460 "only ETHTOOL_A_BITSET_BITS_BIT allowed in ETHTOOL_A_BITSET_BITS"); 461 + kfree(saved_bitmap); 493 462 return -EINVAL; 494 463 } 495 464 ret = ethnl_parse_bit(&idx, &new_val, nbits, bit_attr, no_mask, 496 465 names, extack); 497 - if (ret < 0) 466 + if (ret < 0) { 467 + kfree(saved_bitmap); 498 468 return ret; 469 + } 499 470 old_val = bitmap[idx / 32] & ((u32)1 << (idx % 32)); 500 471 if (new_val != old_val) { 501 472 if (new_val) 502 473 bitmap[idx / 32] |= ((u32)1 << (idx % 32)); 503 474 else 504 475 bitmap[idx / 32] &= ~((u32)1 << (idx % 32)); 505 - *mod = true; 476 + if (!no_mask) 477 + *mod = true; 506 478 } 507 479 } 508 480 481 + if (no_mask && !ethnl_bitmap32_equal(saved_bitmap, bitmap, nbits)) 482 + *mod = true; 483 + 484 + kfree(saved_bitmap); 509 485 return 0; 510 486 } 511 487