Search Linux Wireless

[RFC/HACK] net/compat/wext: allow sending different messages to compat tasks

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Wireless extensions have the unfortunate problem that events
are multicast, and are not independent of pointer size. Thus,
currently 32-bit tasks on 64-bit platforms cannot properly
receive events and fail with all kinds of strange problems,
for instance wpa_supplicant never notices disassociations, due
to the way the 64-bit event looks to a 32-bit process the fact
that the address is all zeroes is lost, it thinks instead it
is 00:01:00:00:00:00 (actually I'm not entirely sure about the
position of the 1 but it's like that).

The same problem existed with the ioctls, until David Miller
fixed those some time ago in an heroic effort.

A different problem caused by this is that we cannot send the
ASSOCREQIE/ASSOCRESPIE events because sending them causes a
32-bit wpa_supplicant on a 64-bit system to overwrite its
internal information, which is worse than it not getting the
information at all -- so we currently resort to sending a
custom string event that it then parses. This, however, has a
severe size limitation we are frequently hitting with modern
access points; this limitation would be lifted if we were able
to send the specific events.

In order to fix these problems, I would like to suggest this
approach I've implemented. When sending an event, we send the
event twice, for 32 and 64 bit tasks. Then, when the event is
read from the socket, the netlink code makes sure to pass out
only the event that is compatible with the task.

To determine whether compat is needed or not, I have used the
MSG_CMSG_COMPAT flag. It may now need a new name, since it can
now be set for recv calls that don't have CMSGs.

To mark the event, I added a new field to the netlink skb cb,
which is only available under COMPAT_NETLINK_MULTICAST, which
is automatically selected for compat and wireless extensions
(a select cannot be used because select isn't transitive).

My patch preserves the socket semantics but if messages are
lost it is possible for a socket to only get the event that
will turn out to be incompatible, in which case a task may
block although select() or poll() returned that the fd is
readable. However, because the messages are sent from process
context, this is very unlikely.

I have not solved one small part of the problem, and I don't
think it is necessary to: if a 32-bit application uses read()
rather than any form of recvmsg() it will still get the wrong
(64-bit) event. However, neither do applications actually do
this, nor would it be a regression.

Signed-off-by: Johannes Berg <johannes@xxxxxxxxxxxxxxxx>
---
 include/linux/netlink.h  |    9 ++++
 include/linux/wireless.h |    8 ++++
 net/Kconfig              |    5 ++
 net/compat.c             |   10 +++--
 net/netlink/af_netlink.c |   23 ++++++++++++
 net/wireless/wext.c      |   89 +++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 140 insertions(+), 4 deletions(-)

--- wireless-testing.orig/net/wireless/wext.c	2009-06-19 16:27:57.000000000 +0200
+++ wireless-testing/net/wireless/wext.c	2009-06-21 13:51:31.000000000 +0200
@@ -417,6 +417,21 @@ static const int event_type_size[] = {
 	IW_EV_QUAL_LEN,			/* IW_HEADER_TYPE_QUAL */
 };
 
+#ifdef CONFIG_COMPAT
+static const int compat_event_type_size[] = {
+	IW_EV_COMPAT_LCP_LEN,		/* IW_HEADER_TYPE_NULL */
+	0,
+	IW_EV_COMPAT_CHAR_LEN,		/* IW_HEADER_TYPE_CHAR */
+	0,
+	IW_EV_COMPAT_UINT_LEN,		/* IW_HEADER_TYPE_UINT */
+	IW_EV_COMPAT_FREQ_LEN,		/* IW_HEADER_TYPE_FREQ */
+	IW_EV_COMPAT_ADDR_LEN,		/* IW_HEADER_TYPE_ADDR */
+	0,
+	IW_EV_COMPAT_POINT_LEN,		/* Without variable payload */
+	IW_EV_COMPAT_PARAM_LEN,		/* IW_HEADER_TYPE_PARAM */
+	IW_EV_COMPAT_QUAL_LEN,		/* IW_HEADER_TYPE_QUAL */
+};
+#endif
 
 /************************ COMMON SUBROUTINES ************************/
 /*
@@ -1348,6 +1363,22 @@ void wireless_send_event(struct net_devi
 	struct sk_buff *skb;
 	struct nlmsghdr *nlh;
 	struct nlattr *nla;
+#ifdef CONFIG_COMPAT
+	struct __compat_iw_event *compat_event;
+	struct compat_iw_point compat_wrqu;
+	struct sk_buff *compskb;
+#endif
+
+	/*
+	 * Nothing in the kernel sends scan events with data, be safe.
+	 * This is necessary because we cannot fix up scan event data
+	 * for compat, due to being contained in 'extra', but normally
+	 * applications are required to retrieve the scan data anyway
+	 * and no data is included in the event, this codifies that
+	 * practice.
+	 */
+	if (WARN_ON(cmd == SIOCGIWSCAN && extra))
+		extra = NULL;
 
 	/* Get the description of the Event */
 	if (cmd <= SIOCIWLAST) {
@@ -1446,9 +1477,67 @@ void wireless_send_event(struct net_devi
 		memcpy(((char *) event) + hdr_len, extra, extra_len);
 
 	nlmsg_end(skb, nlh);
+#ifndef CONFIG_COMPAT
+	/*
+	 * This is under #ifdef because when we send both, we should
+	 * make sure that both skbs exist. That's no guarantee that they
+	 * will also make it to all sockets, but it's very likely since
+	 * the actual sending is done from process context.
+	 */
+	skb_queue_tail(&dev_net(dev)->wext_nlevents, skb);
+	schedule_work(&wireless_nlevent_work);
+#else
+	hdr_len = compat_event_type_size[descr->header_type];
+	event_len = hdr_len + extra_len;
 
+	compskb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
+	if (!compskb) {
+		kfree_skb(skb);
+		return;
+	}
+
+	/* Send via the RtNetlink event channel */
+	nlh = rtnetlink_ifinfo_prep(dev, compskb);
+	if (WARN_ON(!nlh)) {
+		kfree_skb(skb);
+		kfree_skb(compskb);
+		return;
+	}
+
+	/* Add the wireless events in the netlink packet */
+	nla = nla_reserve(compskb, IFLA_WIRELESS, event_len);
+	if (!nla) {
+		kfree_skb(skb);
+		kfree_skb(compskb);
+		return;
+	}
+	compat_event = nla_data(nla);
+
+	compat_event->len = event_len;
+	compat_event->cmd = cmd;
+	if (descr->header_type == IW_HEADER_TYPE_POINT) {
+		compat_wrqu.length = wrqu->data.length;
+		compat_wrqu.flags = wrqu->data.flags;
+		memcpy(&compat_event->pointer,
+			((char *) &compat_wrqu) + IW_EV_COMPAT_POINT_OFF,
+			hdr_len - IW_EV_COMPAT_LCP_LEN);
+		if (extra_len)
+			memcpy(((char *) compat_event) + hdr_len,
+				extra, extra_len);
+	} else {
+		/* extra_len must be zero, so no if (extra) needed */
+		memcpy(&compat_event->pointer, wrqu,
+			hdr_len - IW_EV_COMPAT_LCP_LEN);
+	}
+
+	nlmsg_end(compskb, nlh);
+
+	NETLINK_CB(skb).compat = NL_COMPAT_MC_NOTCOMP;
+	NETLINK_CB(compskb).compat = NL_COMPAT_MC_COMPONLY;
 	skb_queue_tail(&dev_net(dev)->wext_nlevents, skb);
+	skb_queue_tail(&dev_net(dev)->wext_nlevents, compskb);
 	schedule_work(&wireless_nlevent_work);
+#endif
 }
 EXPORT_SYMBOL(wireless_send_event);
 
--- wireless-testing.orig/include/linux/netlink.h	2009-06-19 08:01:12.000000000 +0200
+++ wireless-testing/include/linux/netlink.h	2009-06-19 17:29:10.000000000 +0200
@@ -161,6 +161,12 @@ static inline struct nlmsghdr *nlmsg_hdr
 	return (struct nlmsghdr *)skb->data;
 }
 
+enum nl_compat_mc {
+	NL_COMPAT_MC_ALL = 0,
+	NL_COMPAT_MC_COMPONLY,
+	NL_COMPAT_MC_NOTCOMP,
+};
+
 struct netlink_skb_parms
 {
 	struct ucred		creds;		/* Skb credentials	*/
@@ -170,6 +176,9 @@ struct netlink_skb_parms
 	__u32			loginuid;	/* Login (audit) uid */
 	__u32			sessionid;	/* Session id (audit) */
 	__u32			sid;		/* SELinux security id */
+#ifdef CONFIG_COMPAT_NETLINK_MULTICAST
+	enum nl_compat_mc	compat;
+#endif
 };
 
 #define NETLINK_CB(skb)		(*(struct netlink_skb_parms*)&((skb)->cb))
--- wireless-testing.orig/net/Kconfig	2009-05-18 12:02:05.000000000 +0200
+++ wireless-testing/net/Kconfig	2009-06-19 17:29:10.000000000 +0200
@@ -23,6 +23,11 @@ menuconfig NET
 
 if NET
 
+config COMPAT_NETLINK_MULTICAST
+	def_bool y
+	depends on COMPAT
+	depends on WIRELESS_EXT
+
 menu "Networking options"
 
 source "net/packet/Kconfig"
--- wireless-testing.orig/net/netlink/af_netlink.c	2009-06-19 08:01:29.000000000 +0200
+++ wireless-testing/net/netlink/af_netlink.c	2009-06-21 14:52:52.000000000 +0200
@@ -1358,16 +1358,39 @@ static int netlink_recvmsg(struct kiocb 
 	size_t copied;
 	struct sk_buff *skb;
 	int err;
+#ifdef CONFIG_COMPAT_NETLINK_MULTICAST
+	enum nl_compat_mc compat;
+#endif
 
 	if (flags&MSG_OOB)
 		return -EOPNOTSUPP;
 
 	copied = 0;
 
+#ifdef CONFIG_COMPAT_NETLINK_MULTICAST
+ again:
+#endif
 	skb = skb_recv_datagram(sk, flags, noblock, &err);
 	if (skb == NULL)
 		goto out;
 
+#ifdef CONFIG_COMPAT_NETLINK_MULTICAST
+	compat = NETLINK_CB(skb).compat;
+
+	if (unlikely(compat != NL_COMPAT_MC_ALL)) {
+		bool need_compat = !!(flags & MSG_CMSG_COMPAT);
+		if ((compat == NL_COMPAT_MC_NOTCOMP && need_compat) ||
+		    (compat == NL_COMPAT_MC_COMPONLY && !need_compat)) {
+			kfree_skb(skb);
+			/*
+			 * Compat messages should always be sent in pairs,
+			 * so if this one isn't it, the next one will be.
+			 */
+			goto again;
+		}
+	}
+#endif
+
 	msg->msg_namelen = 0;
 
 	copied = skb->len;
--- wireless-testing.orig/net/compat.c	2009-06-19 17:50:30.000000000 +0200
+++ wireless-testing/net/compat.c	2009-06-19 17:53:13.000000000 +0200
@@ -782,16 +782,18 @@ asmlinkage long compat_sys_socketcall(in
 		ret = sys_socketpair(a0, a1, a[2], compat_ptr(a[3]));
 		break;
 	case SYS_SEND:
-		ret = sys_send(a0, compat_ptr(a1), a[2], a[3]);
+		ret = sys_send(a0, compat_ptr(a1), a[2], a[3] | MSG_CMSG_COMPAT);
 		break;
 	case SYS_SENDTO:
-		ret = sys_sendto(a0, compat_ptr(a1), a[2], a[3], compat_ptr(a[4]), a[5]);
+		ret = sys_sendto(a0, compat_ptr(a1), a[2], a[3] | MSG_CMSG_COMPAT,
+				 compat_ptr(a[4]), a[5]);
 		break;
 	case SYS_RECV:
-		ret = sys_recv(a0, compat_ptr(a1), a[2], a[3]);
+		ret = sys_recv(a0, compat_ptr(a1), a[2], a[3] | MSG_CMSG_COMPAT);
 		break;
 	case SYS_RECVFROM:
-		ret = sys_recvfrom(a0, compat_ptr(a1), a[2], a[3], compat_ptr(a[4]), compat_ptr(a[5]));
+		ret = sys_recvfrom(a0, compat_ptr(a1), a[2], a[3] | MSG_CMSG_COMPAT,
+				   compat_ptr(a[4]), compat_ptr(a[5]));
 		break;
 	case SYS_SHUTDOWN:
 		ret = sys_shutdown(a0,a1);
--- wireless-testing.orig/include/linux/wireless.h	2009-06-21 12:06:56.000000000 +0200
+++ wireless-testing/include/linux/wireless.h	2009-06-21 12:15:24.000000000 +0200
@@ -1132,6 +1132,14 @@ struct __compat_iw_event {
 };
 #define IW_EV_COMPAT_LCP_LEN offsetof(struct __compat_iw_event, pointer)
 #define IW_EV_COMPAT_POINT_OFF offsetof(struct compat_iw_point, length)
+
+/* Size of the various events for compat */
+#define IW_EV_COMPAT_CHAR_LEN	(IW_EV_COMPAT_LCP_LEN + IFNAMSIZ)
+#define IW_EV_COMPAT_UINT_LEN	(IW_EV_COMPAT_LCP_LEN + sizeof(__u32))
+#define IW_EV_COMPAT_FREQ_LEN	(IW_EV_COMPAT_LCP_LEN + sizeof(struct iw_freq))
+#define IW_EV_COMPAT_PARAM_LEN	(IW_EV_COMPAT_LCP_LEN + sizeof(struct iw_param))
+#define IW_EV_COMPAT_ADDR_LEN	(IW_EV_COMPAT_LCP_LEN + sizeof(struct sockaddr))
+#define IW_EV_COMPAT_QUAL_LEN	(IW_EV_COMPAT_LCP_LEN + sizeof(struct iw_quality))
 #define IW_EV_COMPAT_POINT_LEN	\
 	(IW_EV_COMPAT_LCP_LEN + sizeof(struct compat_iw_point) - \
 	 IW_EV_COMPAT_POINT_OFF)


--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Host AP]     [ATH6KL]     [Linux Bluetooth]     [Linux Netdev]     [Kernel Newbies]     [Linux Kernel]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Samba]     [Device Mapper]
  Powered by Linux