lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Mon, 18 Feb 2019 19:21:49 +0100 (CET)
From:   Michal Kubecek <mkubecek@...e.cz>
To:     netdev@...r.kernel.org
Cc:     David Miller <davem@...emloft.net>, Andrew Lunn <andrew@...n.ch>,
        Jakub Kicinski <jakub.kicinski@...ronome.com>,
        Jiri Pirko <jiri@...nulli.us>, linux-kernel@...r.kernel.org
Subject: [RFC PATCH net-next v3 05/21] ethtool: netlink bitset handling

Declare attribute type constants and add helper functions to generate and
parse arbitrary length bit sets.

Signed-off-by: Michal Kubecek <mkubecek@...e.cz>
---
 Documentation/networking/ethtool-netlink.txt |  63 ++
 include/uapi/linux/ethtool_netlink.h         |  32 ++
 net/ethtool/Makefile                         |   2 +-
 net/ethtool/bitset.c                         | 572 +++++++++++++++++++
 net/ethtool/bitset.h                         |  40 ++
 net/ethtool/netlink.h                        |   9 +
 6 files changed, 717 insertions(+), 1 deletion(-)
 create mode 100644 net/ethtool/bitset.c
 create mode 100644 net/ethtool/bitset.h

diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index 09c36e0392a8..205ae4462e9e 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -59,6 +59,69 @@ provided by kernel. In dump requests, device is not specified and kernel
 replies with one message per network device (only those for which the request
 is supported).
 
+Bit sets
+--------
+
+For short bitmaps of (reasonably) fixed length, standard NLA_BITFIELD32 type
+is used. For arbitrary length bitmaps, ethtool netlink uses a nested attribute
+with contents of one of two forms: compact (two binary bitmaps representing
+bit values and mask of affected bits) and bit-by-bit (list of bits identified
+by either index or name).
+
+Compact form: nested (bitset) atrribute contents:
+
+    ETHA_BITSET_LIST	(flag)		no mask, only a list
+    ETHA_BITSET_SIZE	(u32)		number of significant bits
+    ETHA_BITSET_VALUE	(binary)	bitmap of bit values
+    ETHA_BITSET_MASK	(binary)	bitmap of valid bits
+
+Value and mask must have length at least ETHA_BITSET_SIZE bits rounded up to
+a multiple of 32 bits. They consist of 32-bit words in host byte order, words
+ordered from least significant to most significant (i.e. the same way as
+bitmaps are passed with ioctl interface).
+
+For compact form, ETHA_BITSET_SIZE and ETHA_BITSET_VALUE are mandatory.
+Similar to BITFIELD32, a compact form bit set requests to set bits in the mask
+to 1 (if the bit is set in value) or 0 (if not) and preserve the rest. If
+ETHA_BITSET_LIST is present, there is no mask and bitset represents a simple
+list of bits.
+
+Kernel bit set length may differ from userspace length if older application is
+used on newer kernel or vice versa. If userspace bitmap is longer, an error is
+issued only if the request actually tries to set values of some bits not
+recognized by kernel.
+
+Bit-by-bit form: nested (bitset) attribute contents:
+
+    ETHA_BITSET_LIST	(flag)		no mask, only a list
+    ETHA_BITSET_SIZE	(u32)		number of significant bits (optional)
+    ETHA_BITSET_BITS	(nested)	array of bits
+	ETHA_BITSET_BIT
+	    ETHA_BIT_INDEX	(u32)		bit index (0 for LSB)
+            ETHA_BIT_NAME	(string)	bit name
+	    ETHA_BIT_VALUE	(flag)		present if bit is set
+        ETHA_BITSET_BIT
+	...
+
+Bit size is optional for bit-by-bit form. ETHA_BITSET_BITS nest can only
+contain ETHA_BITS_BIT attributes but there can be an arbitrary number of them.
+A bit may be identified by its index or by its name. When used in requests,
+listed bits are set to 0 or 1 according to ETHA_BIT_VALUE, the rest is
+preserved. A request fails if index exceeds kernel bit length or if name is
+not recognized.
+
+When ETHA_BITSET_LIST flag is present, bitset is interpreted as a simple bit
+list. ETHA_BIT_VALUE attributes are not used in such case. Bit list represents
+a bitmap with listed bits set and the rest zero.
+
+In requests, application can use either form. Form used by kernel in reply is
+determined by a flag in flags field of request header. Semantics of value and
+mask depends on the attribute. General idea is that flags control request
+processing, info_mask control which parts of the information are returned in
+"get" request and index identifies a particular subcommand or an object to
+which the request applies.
+
+
 List of message types
 ---------------------
 
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 01896a1ee21a..7f3d401977b4 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -23,6 +23,38 @@ enum {
 	ETHA_DEV_MAX = (__ETHA_DEV_CNT - 1)
 };
 
+/* bit sets */
+
+enum {
+	ETHA_BIT_UNSPEC,
+	ETHA_BIT_INDEX,				/* u32 */
+	ETHA_BIT_NAME,				/* string */
+	ETHA_BIT_VALUE,				/* flag */
+
+	__ETHA_BIT_CNT,
+	ETHA_BIT_MAX = (__ETHA_BIT_CNT - 1)
+};
+
+enum {
+	ETHA_BITS_UNSPEC,
+	ETHA_BITS_BIT,
+
+	__ETHA_BITS_CNT,
+	ETHA_BITS_MAX = (__ETHA_BITS_CNT - 1)
+};
+
+enum {
+	ETHA_BITSET_UNSPEC,
+	ETHA_BITSET_LIST,			/* flag */
+	ETHA_BITSET_SIZE,			/* u32 */
+	ETHA_BITSET_BITS,			/* nest - ETHA_BITS_* */
+	ETHA_BITSET_VALUE,			/* binary */
+	ETHA_BITSET_MASK,			/* binary */
+
+	__ETHA_BITSET_CNT,
+	ETHA_BITSET_MAX = (__ETHA_BITSET_CNT - 1)
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile
index f30e0da88be5..482fdb9380fa 100644
--- a/net/ethtool/Makefile
+++ b/net/ethtool/Makefile
@@ -4,4 +4,4 @@ obj-y				+= ioctl.o
 
 obj-$(CONFIG_ETHTOOL_NETLINK)	+= ethtool_nl.o
 
-ethtool_nl-y	:= netlink.o
+ethtool_nl-y	:= netlink.o bitset.o
diff --git a/net/ethtool/bitset.c b/net/ethtool/bitset.c
new file mode 100644
index 000000000000..8f5c60999a96
--- /dev/null
+++ b/net/ethtool/bitset.c
@@ -0,0 +1,572 @@
+// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+
+#include <linux/ethtool_netlink.h>
+#include <linux/bitmap.h>
+#include "netlink.h"
+#include "bitset.h"
+
+static bool ethnl_test_bit(const void *val, unsigned int index, bool is_u32)
+{
+	if (!val)
+		return true;
+	else if (is_u32)
+		return ((const u32 *)val)[index / 32] & (1U << (index % 32));
+	else
+		return test_bit(index, val);
+}
+
+static void __bitmap_to_u32(u32 *dst, const void *src, unsigned int size,
+			    bool is_u32)
+{
+	unsigned int full_words = size / 32;
+	const u32 *src32 = src;
+
+	if (!is_u32) {
+		bitmap_to_arr32(dst, src, size);
+		return;
+	}
+
+	memcpy(dst, src32, full_words * sizeof(u32));
+	if (size % 32 != 0)
+		dst[full_words] = src32[full_words] & ((1U << (size % 32)) - 1);
+}
+
+/* convert standard kernel bitmap (long sized words) to ethtool one (u32 words)
+ * bitmap_to_arr32() is not guaranteed to do "in place" conversion correctly;
+ * moreover, we can use the fact that the conversion is no-op except for 64-bit
+ * big endian architectures
+ */
+#if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN)
+void ethnl_bitmap_to_u32(unsigned long *bitmap, unsigned int nwords)
+{
+	u32 *dst = (u32 *)bitmap;
+	unsigned int i;
+
+	for (i = 0; i < nwords; i++) {
+		unsigned long tmp = READ_ONCE(bitmap[i]);
+
+		dst[2 * i] = tmp & 0xffffffff;
+		dst[2 * i + 1] = tmp >> 32;
+	}
+}
+#endif
+
+static const char *bit_name(const void *names, bool legacy, unsigned int idx)
+{
+	const char (*const legacy_names)[ETH_GSTRING_LEN] = names;
+	const char *const *simple_names = names;
+
+	return legacy ? legacy_names[idx] : simple_names[idx];
+}
+
+/* calculate size for a bitset attribute
+ * see ethnl_put_bitset() for arguments
+ */
+static int __ethnl_bitset_size(unsigned int size, const void *val,
+			       const void *mask, const void *names,
+			       unsigned int flags)
+{
+	const bool legacy = flags & ETHNL_BITSET_LEGACY_NAMES;
+	const bool compact = flags & ETHNL_BITSET_COMPACT;
+	const bool is_list = flags & ETHNL_BITSET_LIST;
+	const bool is_u32 = flags & ETHNL_BITSET_U32;
+	unsigned int nwords = DIV_ROUND_UP(size, 32);
+	unsigned int len = 0;
+
+	if (WARN_ON(!compact && !names))
+		return -EINVAL;
+	/* list flag */
+	if (flags & ETHNL_BITSET_LIST)
+		len += nla_total_size(sizeof(u32));
+	/* size */
+	len += nla_total_size(sizeof(u32));
+
+	if (compact) {
+		/* values, mask */
+		len += 2 * nla_total_size(nwords * sizeof(u32));
+	} else {
+		unsigned int bits_len = 0;
+		unsigned int bit_len, i;
+
+		for (i = 0; i < size; i++) {
+			const char *name = bit_name(names, legacy, i) ?: "";
+
+			if ((is_list || mask) &&
+			    !ethnl_test_bit(is_list ? val : mask, i, is_u32))
+				continue;
+			/* index */
+			bit_len = nla_total_size(sizeof(u32));
+			/* name */
+			bit_len += ethnl_str_size(name);
+			/* value */
+			if (!is_list && ethnl_test_bit(val, i, is_u32))
+				bit_len += nla_total_size(0);
+
+			/* bit nest */
+			bits_len += nla_total_size(bit_len);
+		}
+		/* bits nest */
+		len += nla_total_size(bits_len);
+	}
+
+	/* outermost nest */
+	return nla_total_size(len);
+}
+
+int ethnl_bitset_size(unsigned int size, const unsigned long *val,
+		      const unsigned long *mask, const void *names,
+		      unsigned int flags)
+{
+	return __ethnl_bitset_size(size, val, mask, names,
+				   flags & ~ETHNL_BITSET_U32);
+}
+
+int ethnl_bitset32_size(unsigned int size, const u32 *val, const u32 *mask,
+			const void *names, unsigned int flags)
+{
+	return __ethnl_bitset_size(size, val, mask, names,
+				   flags | ETHNL_BITSET_U32);
+}
+
+/* put bitset into a message
+ * skb:      skb with the message
+ * attrtype: attribute type for the bitset
+ * size:     size of the set in bits
+ * val:      bitset values
+ * mask:     mask of valid bits; NULL is interpreted as "all bits"
+ * names:    bit names (only used for verbose format)
+ * flags:    combination of ETHNL_BITSET_* flags
+ */
+static int __ethnl_put_bitset(struct sk_buff *skb, int attrtype,
+			      unsigned int size, const void *val,
+			      const void *mask, const void *names,
+			      unsigned int flags)
+{
+	const bool legacy = flags & ETHNL_BITSET_LEGACY_NAMES;
+	const bool compact = flags & ETHNL_BITSET_COMPACT;
+	const bool is_list = flags & ETHNL_BITSET_LIST;
+	const bool is_u32 = flags & ETHNL_BITSET_U32;
+	struct nlattr *nest;
+	struct nlattr *attr;
+	int ret;
+
+	if (WARN_ON(!compact && !names))
+		return -EINVAL;
+	nest = ethnl_nest_start(skb, attrtype);
+	if (!nest)
+		return -EMSGSIZE;
+
+	ret = -EMSGSIZE;
+	if (is_list && nla_put_flag(skb, ETHA_BITSET_LIST))
+		goto err;
+	if (nla_put_u32(skb, ETHA_BITSET_SIZE, size))
+		goto err;
+	if (compact) {
+		unsigned int bytesize = DIV_ROUND_UP(size, 32) * sizeof(u32);
+
+		attr = nla_reserve(skb, ETHA_BITSET_VALUE, bytesize);
+		if (!attr)
+			goto err;
+		__bitmap_to_u32(nla_data(attr), val, size, is_u32);
+		if (mask) {
+			attr = nla_reserve(skb, ETHA_BITSET_MASK, bytesize);
+			if (!attr)
+				goto err;
+			__bitmap_to_u32(nla_data(attr), mask, size, is_u32);
+		}
+	} else {
+		struct nlattr *bits;
+		unsigned int i;
+
+		bits = ethnl_nest_start(skb, ETHA_BITSET_BITS);
+		if (!bits)
+			goto err;
+		for (i = 0; i < size; i++) {
+			const char *name = bit_name(names, legacy, i) ?: "";
+
+			if ((is_list || mask) &&
+			    !ethnl_test_bit(is_list ? val : mask, i, is_u32))
+				continue;
+			attr = ethnl_nest_start(skb, ETHA_BITS_BIT);
+			if (!attr ||
+			    nla_put_u32(skb, ETHA_BIT_INDEX, i) ||
+			    nla_put_string(skb, ETHA_BIT_NAME, name))
+				goto err;
+			if (!is_list && ethnl_test_bit(val, i, is_u32) &&
+			    nla_put_flag(skb, ETHA_BIT_VALUE))
+				goto err;
+			nla_nest_end(skb, attr);
+		}
+		nla_nest_end(skb, bits);
+	}
+
+	nla_nest_end(skb, nest);
+	return 0;
+err:
+	nla_nest_cancel(skb, nest);
+	return ret;
+}
+
+int ethnl_put_bitset(struct sk_buff *skb, int attrtype, unsigned int size,
+		     const unsigned long *val, const unsigned long *mask,
+		     const void *names, unsigned int flags)
+{
+	return __ethnl_put_bitset(skb, attrtype, size, val, mask, names,
+				  flags & ~ETHNL_BITSET_U32);
+}
+
+int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, unsigned int size,
+		       const u32 *val, const u32 *mask, const void *names,
+		       unsigned int flags)
+{
+	return __ethnl_put_bitset(skb, attrtype, size, val, mask, names,
+				  flags | ETHNL_BITSET_U32);
+}
+
+static const struct nla_policy bitset_policy[ETHA_BITSET_MAX + 1] = {
+	[ETHA_BITSET_UNSPEC]		= { .type = NLA_REJECT },
+	[ETHA_BITSET_LIST]		= { .type = NLA_FLAG },
+	[ETHA_BITSET_SIZE]		= { .type = NLA_U32 },
+	[ETHA_BITSET_BITS]		= { .type = NLA_NESTED },
+	[ETHA_BITSET_VALUE]		= { .type = NLA_BINARY },
+	[ETHA_BITSET_MASK]		= { .type = NLA_BINARY },
+};
+
+static const struct nla_policy bit_policy[ETHA_BIT_MAX + 1] = {
+	[ETHA_BIT_UNSPEC]		= { .type = NLA_REJECT },
+	[ETHA_BIT_INDEX]		= { .type = NLA_U32 },
+	[ETHA_BIT_NAME]			= { .type = NLA_NUL_STRING },
+	[ETHA_BIT_VALUE]		= { .type = NLA_FLAG },
+};
+
+static int ethnl_name_to_idx(const void *names, bool legacy,
+			     unsigned int n_names, const char *name,
+			     unsigned int name_len)
+{
+	unsigned int i;
+
+	for (i = 0; i < n_names; i++) {
+		const char *bname = bit_name(names, legacy, i);
+
+		if (bname && !strncmp(bname, name, name_len) &&
+		    strlen(bname) <= name_len)
+			return i;
+	}
+
+	return n_names;
+}
+
+static int ethnl_update_bit(unsigned long *bitmap, unsigned long *bitmask,
+			    unsigned int nbits, const struct nlattr *bit_attr,
+			    bool is_list, const void *names, bool legacy,
+			    struct genl_info *info)
+{
+	struct nlattr *tb[ETHA_BIT_MAX + 1];
+	int ret, idx;
+
+	if (nla_type(bit_attr) != ETHA_BITS_BIT) {
+		ETHNL_SET_ERRMSG(info,
+				 "ETHA_BITSET_BITS can contain only ETHA_BITS_BIT");
+		return genl_err_attr(info, -EINVAL, bit_attr);
+	}
+	ret = nla_parse_nested(tb, ETHA_BIT_MAX, bit_attr, bit_policy,
+			       info->extack);
+	if (ret < 0)
+		return ret;
+
+	if (tb[ETHA_BIT_INDEX]) {
+		const char *name;
+
+		idx = nla_get_u32(tb[ETHA_BIT_INDEX]);
+		if (idx >= nbits) {
+			ETHNL_SET_ERRMSG(info, "bit index too high");
+			return genl_err_attr(info, -EOPNOTSUPP,
+					     tb[ETHA_BIT_INDEX]);
+		}
+		name = bit_name(names, legacy, idx);
+		if (tb[ETHA_BIT_NAME] && name &&
+		    strncmp(nla_data(tb[ETHA_BIT_NAME]), name,
+			    nla_len(tb[ETHA_BIT_NAME]))) {
+			ETHNL_SET_ERRMSG(info, "bit index and name mismatch");
+			return genl_err_attr(info, -EINVAL, bit_attr);
+		}
+	} else if (tb[ETHA_BIT_NAME]) {
+		idx = ethnl_name_to_idx(names, legacy, nbits,
+					nla_data(tb[ETHA_BIT_NAME]),
+					nla_len(tb[ETHA_BIT_NAME]));
+		if (idx >= nbits) {
+			ETHNL_SET_ERRMSG(info, "bit name not found");
+			return genl_err_attr(info, -EOPNOTSUPP,
+					     tb[ETHA_BIT_NAME]);
+		}
+	} else {
+		ETHNL_SET_ERRMSG(info, "neither bit index nor name specified");
+		return genl_err_attr(info, -EINVAL, bit_attr);
+	}
+
+	if (is_list || tb[ETHA_BIT_VALUE])
+		set_bit(idx, bitmap);
+	else
+		clear_bit(idx, bitmap);
+	if (!is_list || bitmask)
+		set_bit(idx, bitmask);
+	return 0;
+}
+
+int ethnl_bitset_is_compact(const struct nlattr *bitset, bool *compact)
+{
+	struct nlattr *tb[ETHA_BITSET_MAX + 1];
+	int ret;
+
+	ret = nla_parse_nested(tb, ETHA_BITSET_MAX, bitset, bitset_policy,
+			       NULL);
+	if (ret < 0)
+		return ret;
+
+	if (tb[ETHA_BITSET_BITS]) {
+		if (tb[ETHA_BITSET_VALUE] || tb[ETHA_BITSET_MASK])
+			return -EINVAL;
+		*compact = false;
+		return 0;
+	}
+	if (!tb[ETHA_BITSET_SIZE] || !tb[ETHA_BITSET_VALUE])
+		return -EINVAL;
+
+	*compact = true;
+	return 0;
+}
+
+/* 64-bit long endian is the only case when u32 based bitmap and unsigned long
+ * based bitmap layouts differ
+ */
+#if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN)
+/* dst &= src */
+static void __bitmap_and_u32(unsigned long *dst, const u32 *src,
+			     unsigned int nbits)
+{
+	unsigned long op;
+
+	while (nbits >= BITS_PER_LONG) {
+		op = src[0] | ((unsigned long)src[1] << 32);
+		*dst &= op;
+
+		dst++;
+		src += 2;
+		nbits -= BITS_PER_LONG;
+	}
+
+	if (!nbits)
+		return;
+	op = src[0];
+	if (nbits > 32)
+		op |= ((unsigned long)src[1] << 32);
+	*dst = (op & BITMAP_LAST_WORD_MASK(nbits));
+}
+
+/* map1 == map2 */
+static bool __bitmap_equal_u32(const unsigned long *map1, const u32 *map2,
+			       unsigned int nbits)
+{
+	unsigned long dword;
+
+	while (nbits >= BITS_PER_LONG) {
+		dword = map2[0] | ((unsigned long)map2[1] << 32);
+		if (*map1 != dword)
+			return false;
+
+		map1++;
+		map2 += 2;
+		nbits -= BITS_PER_LONG;
+	}
+
+	if (!nbits)
+		return true;
+	dword = map2[0];
+	if (nbits > 32)
+		dword |= ((unsigned long)map2[1] << 32);
+	return !((*map1 ^ dword) & BITMAP_LAST_WORD_MASK(nbits));
+}
+#else
+/* On 32-bit and 64-bit LE, unsigned long and u32 bitmap layout is the same
+ * but we must not write past dst buffer if the number of words is odd.
+ */
+static void __bitmap_and_u32(unsigned long *dst, const u32 *src,
+			     unsigned int nbits)
+{
+	u32 *dst32 = (u32 *)dst;
+
+	while (nbits >= 32) {
+		*dst32++ &= *src++;
+		nbits -= 32;
+	}
+	if (!nbits)
+		return;
+	*dst32 &= (*src & ((1U << nbits) - 1));
+}
+
+static bool __bitmap_equal_u32(const unsigned long *map1, const u32 *map2,
+			       unsigned int nbits)
+{
+	unsigned int full_words = nbits / 32;
+	u32 last_word_mask;
+	u32 *map1_32 = (u32 *)map1;
+
+	if (memcmp(map1, map2, full_words * BITS_PER_BYTE))
+		return false;
+	if (!(nbits % 32))
+		return true;
+	last_word_mask = (1U << (nbits % 32)) - 1;
+	return !((map1_32[full_words] ^ map2[full_words]) & last_word_mask);
+}
+#endif
+
+/* copy unsigned long bitmap to unsigned long or u32 */
+static void __bitmap_to_any(void *dst, const unsigned long *src,
+			    unsigned int nbits, bool dst_is_u32)
+{
+	if (dst_is_u32)
+		bitmap_to_arr32(dst, src, nbits);
+	else
+		bitmap_copy(dst, src, nbits);
+}
+
+static bool __bitmap_equal_any(const unsigned long *map1, const void *map2,
+			       unsigned int nbits, bool is_u32)
+{
+	if (!is_u32)
+		return bitmap_equal(map1, map2, nbits);
+	else
+		return __bitmap_equal_u32(map1, map2, nbits);
+}
+
+static bool __ethnl_update_bitset(void *bitmap, void *bitmask,
+				  unsigned int nbits, const struct nlattr *attr,
+				  int *err, const void *names, bool legacy,
+				  struct genl_info *info, bool is_u32)
+{
+	struct nlattr *tb[ETHA_BITSET_MAX + 1];
+	unsigned int change_bits = 0;
+	unsigned int max_bits = 0;
+	unsigned long *val, *mask;
+	bool mod = false;
+	bool is_list;
+
+	*err = 0;
+	if (!attr)
+		return mod;
+	*err = nla_parse_nested(tb, ETHA_BITSET_MAX, attr, bitset_policy,
+				info->extack);
+	if (*err < 0)
+		return mod;
+	*err = -EINVAL;
+	if (tb[ETHA_BITSET_BITS] &&
+	    (tb[ETHA_BITSET_VALUE] || tb[ETHA_BITSET_MASK]))
+		return mod;
+	if (!tb[ETHA_BITSET_BITS] &&
+	    (!tb[ETHA_BITSET_SIZE] || !tb[ETHA_BITSET_VALUE]))
+		return mod;
+	is_list = (tb[ETHA_BITSET_LIST] != NULL);
+	if (is_list && tb[ETHA_BITSET_MASK])
+		return mod;
+
+	/* To let new userspace to work with old kernel, we allow bitmaps
+	 * from userspace to be longer than kernel ones and only issue an
+	 * error if userspace actually tries to change a bit not existing
+	 * in kernel.
+	 */
+	if (tb[ETHA_BITSET_SIZE])
+		change_bits = nla_get_u32(tb[ETHA_BITSET_SIZE]);
+	max_bits = max_t(unsigned int, nbits, change_bits);
+	mask = bitmap_zalloc(max_bits, GFP_KERNEL);
+	val = bitmap_zalloc(max_bits, GFP_KERNEL);
+
+	if (tb[ETHA_BITSET_BITS]) {
+		struct nlattr *bit_attr;
+		int rem;
+
+		if (is_list)
+			bitmap_fill(mask, nbits);
+		else if (is_u32)
+			bitmap_from_arr32(val, bitmap, nbits);
+		else
+			bitmap_copy(val, bitmap, nbits);
+		nla_for_each_nested(bit_attr, tb[ETHA_BITSET_BITS], rem) {
+			*err = ethnl_update_bit(val, mask, nbits, bit_attr,
+						is_list, names, legacy, info);
+			if (*err < 0)
+				goto out_free;
+		}
+		if (bitmask)
+			__bitmap_to_any(bitmask, mask, nbits, is_u32);
+	} else {
+		unsigned int change_words = DIV_ROUND_UP(change_bits, 32);
+
+		*err = 0;
+		if (change_bits == 0 && tb[ETHA_BITSET_MASK])
+			goto out_free;
+		*err = -EINVAL;
+		if (!tb[ETHA_BITSET_VALUE])
+			goto out_free;
+		if (nla_len(tb[ETHA_BITSET_VALUE]) < change_words * sizeof(u32))
+			goto out_free;
+		if (tb[ETHA_BITSET_MASK] &&
+		    nla_len(tb[ETHA_BITSET_MASK]) < change_words * sizeof(u32))
+			goto out_free;
+
+		bitmap_from_arr32(val, nla_data(tb[ETHA_BITSET_VALUE]),
+				  change_bits);
+		if (tb[ETHA_BITSET_MASK])
+			bitmap_from_arr32(mask, nla_data(tb[ETHA_BITSET_MASK]),
+					  change_bits);
+		else
+			bitmap_fill(mask, nbits);
+
+		if (nbits < change_bits) {
+			unsigned int idx = find_next_bit(mask, max_bits, nbits);
+
+			*err = -EINVAL;
+			if (idx < max_bits)
+				goto out_free;
+		}
+
+		if (bitmask)
+			__bitmap_to_any(bitmask, mask, nbits, is_u32);
+		if (!is_list) {
+			bitmap_and(val, val, mask, nbits);
+			bitmap_complement(mask, mask, nbits);
+			if (is_u32)
+				__bitmap_and_u32(mask, bitmap, nbits);
+			else
+				bitmap_and(mask, mask, bitmap, nbits);
+			bitmap_or(val, val, mask, nbits);
+		}
+	}
+
+	mod = !__bitmap_equal_any(val, bitmap, nbits, is_u32);
+	if (mod)
+		__bitmap_to_any(bitmap, val, nbits, is_u32);
+
+	*err = 0;
+out_free:
+	bitmap_free(val);
+	bitmap_free(mask);
+	return mod;
+}
+
+bool ethnl_update_bitset(unsigned long *bitmap, unsigned long *bitmask,
+			 unsigned int nbits, const struct nlattr *attr,
+			 int *err, const void *names, bool legacy,
+			 struct genl_info *info)
+{
+	return __ethnl_update_bitset(bitmap, bitmask, nbits, attr, err, names,
+				     legacy, info, false);
+}
+
+bool ethnl_update_bitset32(u32 *bitmap, u32 *bitmask, unsigned int nbits,
+			   const struct nlattr *attr, int *err,
+			   const void *names, bool legacy,
+			   struct genl_info *info)
+{
+	return __ethnl_update_bitset(bitmap, bitmask, nbits, attr, err, names,
+				     legacy, info, true);
+}
diff --git a/net/ethtool/bitset.h b/net/ethtool/bitset.h
new file mode 100644
index 000000000000..761d0c47fe23
--- /dev/null
+++ b/net/ethtool/bitset.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef _NET_ETHTOOL_BITSET_H
+#define _NET_ETHTOOL_BITSET_H
+
+/* when set, value and mask bitmaps are arrays of u32, when not, arrays of
+ * unsigned long
+ */
+#define ETHNL_BITSET_U32		BIT(0)
+/* generate a compact format bitset */
+#define ETHNL_BITSET_COMPACT		BIT(1)
+/* generate a bit list */
+#define ETHNL_BITSET_LIST		BIT(2)
+/* when set, names are interpreted as legacy string set (an array of
+ * char[ETH_GSTRING_LEN]), when not, as a simple array of char *
+ */
+#define ETHNL_BITSET_LEGACY_NAMES	BIT(3)
+
+int ethnl_bitset_is_compact(const struct nlattr *bitset, bool *compact);
+int ethnl_bitset_size(unsigned int size, const unsigned long *val,
+		      const unsigned long *mask, const void *names,
+		      unsigned int flags);
+int ethnl_bitset32_size(unsigned int size, const u32 *val, const u32 *mask,
+			const void *names, unsigned int flags);
+int ethnl_put_bitset(struct sk_buff *skb, int attrtype, unsigned int size,
+		     const unsigned long *val, const unsigned long *mask,
+		     const void *names, unsigned int flags);
+int ethnl_put_bitset32(struct sk_buff *skb, int attrtype, unsigned int size,
+		       const u32 *val, const u32 *mask, const void *names,
+		       unsigned int flags);
+bool ethnl_update_bitset(unsigned long *bitmap, unsigned long *bitmask,
+			 unsigned int nbits, const struct nlattr *attr,
+			 int *err, const void *names, bool legacy,
+			 struct genl_info *info);
+bool ethnl_update_bitset32(u32 *bitmap, u32 *bitmask, unsigned int nbits,
+			   const struct nlattr *attr, int *err,
+			   const void *names, bool legacy,
+			   struct genl_info *info);
+
+#endif /* _NET_ETHTOOL_BITSET_H */
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 36a397656127..ba638eb5a99a 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -20,6 +20,15 @@ struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
 				 u16 dev_attrtype, struct genl_info *info,
 				 void **ehdrp);
 
+#if BITS_PER_LONG == 64 && defined(__BIG_ENDIAN)
+void ethnl_bitmap_to_u32(unsigned long *bitmap, unsigned int nwords);
+#else
+static inline void ethnl_bitmap_to_u32(unsigned long *bitmap,
+				       unsigned int nwords)
+{
+}
+#endif
+
 static inline int ethnl_str_size(const char *s)
 {
 	return nla_total_size(strlen(s) + 1);
-- 
2.20.1

Powered by blists - more mailing lists